Skip to content

Commit

Permalink
qemu: ask to sign QEMU binary when the binary is not properly signed
Browse files Browse the repository at this point in the history
Workaround for issue 1742 .
This workaround is needed because Homebrew's QEMU binary is not properly signed since v8.0.4.

Signed-off-by: Akihiro Suda <[email protected]>
  • Loading branch information
AkihiroSuda committed Aug 14, 2023
1 parent 18c029b commit 2a6cbfb
Showing 1 changed file with 97 additions and 0 deletions.
97 changes: 97 additions & 0 deletions pkg/qemu/qemu_driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ import (
"text/template"
"time"

"github.com/AlecAivazis/survey/v2"
"github.com/digitalocean/go-qemu/qmp"
"github.com/digitalocean/go-qemu/qmp/raw"
"github.com/lima-vm/lima/pkg/driver"
"github.com/lima-vm/lima/pkg/limayaml"
"github.com/lima-vm/lima/pkg/networks/usernet"
"github.com/lima-vm/lima/pkg/store"
"github.com/lima-vm/lima/pkg/store/filenames"
"github.com/mattn/go-isatty"
"github.com/sirupsen/logrus"
)

Expand Down Expand Up @@ -59,6 +61,76 @@ func (l *LimaQemuDriver) CreateDisk() error {
return EnsureDisk(qCfg)
}

// isHVF returns true if qArgs enables HVF.
// isHVF is over-approximated.
func isHVF(qArgs []string) bool {
if runtime.GOOS != "darwin" {
return false
}
for _, s := range qArgs {
// over-approximation
if strings.Contains(s, "hvf") {
return true
}
}
return false
}

// isQEMUBinarySignedWithComAppleSecurityHypervisor returns an error
// if the binary is not signed, or the sign is invalid, or not associated with
// the "com.apple.security.hypervisor" entitlement.
func isQEMUBinarySignedWithComAppleSecurityHypervisor(qExe string) error {
cmd := exec.Command("codesign", "--verify", qExe)
out, err := cmd.CombinedOutput()
logrus.WithError(err).Debugf("Executed %v: out=%q", cmd.Args, string(out))
if err != nil {
return fmt.Errorf("failed to run %v: %w (out=%q)", cmd.Args, string(out))
}

cmd = exec.Command("codesign", "--display", "--entitlements", "-", "--xml", qExe)
out, err = cmd.CombinedOutput()
logrus.WithError(err).Debugf("Executed %v: out=%q", cmd.Args, string(out))
if err != nil {
return fmt.Errorf("failed to run %v: %w (out=%q)", cmd.Args, string(out))
}
if !strings.Contains(string(out), "com.apple.security.hypervisor") {
return fmt.Errorf("binary %q seems signed but lacking the \"com.apple.security.hypervisor\" entitlement")
}
return nil
}

// signQEMUBinaryWithComAppleSecurityHypervisor signs QEMU binary with the "com.apple.security.hypervisor" entitlement.
//
// On Homebrew, QEMU binaries are usually already signed, but Homebrew's signing infrastructure is broken for Intel as of Augest 2023.
// https://github.com/lima-vm/lima/issues/1742
func signQEMUBinaryWithComAppleSecurityHypervisor(qExe string) error {
ent, err := os.CreateTemp("", "lima-qemu-entitlements-*.xml")
if err != nil {
return fmt.Errorf("failed to create a temporary file for signing QEMU binary: %w")
}
entName := ent.Name()
defer os.RemoveAll(entName)
const entXML = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.hypervisor</key>
<true/>
</dict>
</plist>`
if _, err = ent.Write([]byte(entXML)); err != nil {
return fmt.Errorf("Failed to write to a temporary file %q for signing QEMU binary: %w", entName, err)
}
ent.Close()
signCmd := exec.Command("codesign", "--sign", "-", "--entitlements", entName, "--force", qExe)
out, err := signCmd.CombinedOutput()
logrus.WithError(err).Debugf("Executed %v: out=%q", signCmd.Args, string(out))
if err != nil {
return fmt.Errorf("failed to run %v: %w (out=%q)", signCmd.Args, err, string(out))
}
return nil
}

func (l *LimaQemuDriver) Start(ctx context.Context) (chan error, error) {
ctx, cancel := context.WithCancel(ctx)
defer func() {
Expand All @@ -78,6 +150,31 @@ func (l *LimaQemuDriver) Start(ctx context.Context) (chan error, error) {
return nil, err
}

if isHVF(qArgs) {
if isSignedErr := isQEMUBinarySignedWithComAppleSecurityHypervisor(qExe); isSignedErr != nil {
logrus.WithError(isSignedErr).Warnf("QEMU binary %q is not properly signed with the \"com.apple.security.hypervisor\" entitlement", qExe)
var ans bool
if isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd()) {
prompt := &survey.Confirm{
Message: fmt.Sprintf("Try to sign %q with the \"com.apple.security.hypervisor\" entitlement?", qExe),
Default: true,
}
if askErr := survey.AskOne(prompt, &ans); askErr != nil {
logrus.WithError(askErr).Warn("No answer was given")
}
}
if ans {
if signErr := signQEMUBinaryWithComAppleSecurityHypervisor(qExe); signErr != nil {
logrus.WithError(err).Warnf("Failed to sign %q", qExe)
}
} else {
logrus.Warn("You have to sign the QEMU binary with the \"com.apple.security.hypervisor\" entitlement manually. See https://github.com/lima-vm/lima/issues/1742 .")
}
} else {
logrus.Infof("QEMU binary %q seems properly signed with the \"com.apple.security.hypervisor\" entitlement", qExe)
}
}

var vhostCmds []*exec.Cmd
if *l.Yaml.MountType == limayaml.VIRTIOFS {
vhostExe, err := FindVirtiofsd(qExe)
Expand Down

0 comments on commit 2a6cbfb

Please sign in to comment.