Skip to content

Commit

Permalink
Merge pull request lima-vm#1743 from AkihiroSuda/sign-qemu-binary
Browse files Browse the repository at this point in the history
qemu: ask to sign QEMU binary when the binary is not properly signed
  • Loading branch information
AkihiroSuda authored Aug 14, 2023
2 parents 18c029b + c0b48a9 commit bee0502
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 4 deletions.
94 changes: 94 additions & 0 deletions pkg/qemu/entitlementutil/entitlementutil.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Package entitlementutil provides a workaround for https://github.com/lima-vm/lima/issues/1742
package entitlementutil

import (
"fmt"
"os"
"os/exec"
"strings"

"github.com/AlecAivazis/survey/v2"
"github.com/mattn/go-isatty"
"github.com/sirupsen/logrus"
)

// IsSigned 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 IsSigned(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, err, 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, err, 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", qExe)
}
return nil
}

func Sign(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", err)
}
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
}

// AskToSignIfNotSignedProperly asks to sign the 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 AskToSignIfNotSignedProperly(qExe string) {
if isSignedErr := IsSigned(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 := Sign(qExe); signErr != nil {
logrus.WithError(signErr).Warnf("Failed to sign %q", qExe)
} else {
logrus.Infof("Successfully signed %q with the \"com.apple.security.hypervisor\" entitlement", 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)
}
}
8 changes: 4 additions & 4 deletions pkg/qemu/qemu.go
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ func qemuMachine(arch limayaml.Arch) string {

func Cmdline(cfg Config) (string, []string, error) {
y := cfg.LimaYAML
exe, args, err := getExe(*y.Arch)
exe, args, err := Exe(*y.Arch)
if err != nil {
return "", nil, err
}
Expand All @@ -490,7 +490,7 @@ func Cmdline(cfg Config) (string, []string, error) {
}

// Architecture
accel := getAccel(*y.Arch)
accel := Accel(*y.Arch)
if !strings.Contains(string(features.AccelHelp), accel) {
return "", nil, fmt.Errorf("accelerator %q is not supported by %s", accel, exe)
}
Expand Down Expand Up @@ -1003,7 +1003,7 @@ func qemuArch(arch limayaml.Arch) string {
return arch
}

func getExe(arch limayaml.Arch) (string, []string, error) {
func Exe(arch limayaml.Arch) (string, []string, error) {
exeBase := "qemu-system-" + qemuArch(arch)
var args []string
envK := "QEMU_SYSTEM_" + strings.ToUpper(qemuArch(arch))
Expand All @@ -1024,7 +1024,7 @@ func getExe(arch limayaml.Arch) (string, []string, error) {
return exe, args, nil
}

func getAccel(arch limayaml.Arch) string {
func Accel(arch limayaml.Arch) string {
if limayaml.IsNativeArch(arch) {
switch runtime.GOOS {
case "darwin":
Expand Down
15 changes: 15 additions & 0 deletions pkg/start/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"text/template"
"time"

"github.com/lima-vm/lima/pkg/driver"
"github.com/lima-vm/lima/pkg/driverutil"
"github.com/lima-vm/lima/pkg/qemu"
"github.com/lima-vm/lima/pkg/qemu/entitlementutil"

"github.com/lima-vm/lima/pkg/downloader"
"github.com/lima-vm/lima/pkg/fileutils"
Expand Down Expand Up @@ -101,6 +104,18 @@ func Start(ctx context.Context, inst *store.Instance) error {

haSockPath := filepath.Join(inst.Dir, filenames.HostAgentSock)

// Ask the user to sign the qemu binary with the "com.apple.security.hypervisor" if needed.
// Workaround for https://github.com/lima-vm/lima/issues/1742
if runtime.GOOS == "darwin" && inst.VMType == limayaml.QEMU {
qExe, _, err := qemu.Exe(inst.Arch)
if err != nil {
return fmt.Errorf("failed to find the QEMU binary for the architecture %q: %w", inst.Arch, err)
}
if accel := qemu.Accel(inst.Arch); accel == "hvf" {
entitlementutil.AskToSignIfNotSignedProperly(qExe)
}
}

prepared, err := Prepare(ctx, inst)
if err != nil {
return err
Expand Down

0 comments on commit bee0502

Please sign in to comment.