Skip to content

Commit bee0502

Browse files
authored
Merge pull request #1743 from AkihiroSuda/sign-qemu-binary
qemu: ask to sign QEMU binary when the binary is not properly signed
2 parents 18c029b + c0b48a9 commit bee0502

File tree

3 files changed

+113
-4
lines changed

3 files changed

+113
-4
lines changed

Diff for: pkg/qemu/entitlementutil/entitlementutil.go

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Package entitlementutil provides a workaround for https://github.com/lima-vm/lima/issues/1742
2+
package entitlementutil
3+
4+
import (
5+
"fmt"
6+
"os"
7+
"os/exec"
8+
"strings"
9+
10+
"github.com/AlecAivazis/survey/v2"
11+
"github.com/mattn/go-isatty"
12+
"github.com/sirupsen/logrus"
13+
)
14+
15+
// IsSigned returns an error if the binary is not signed, or the sign is invalid,
16+
// or not associated with the "com.apple.security.hypervisor" entitlement.
17+
func IsSigned(qExe string) error {
18+
cmd := exec.Command("codesign", "--verify", qExe)
19+
out, err := cmd.CombinedOutput()
20+
logrus.WithError(err).Debugf("Executed %v: out=%q", cmd.Args, string(out))
21+
if err != nil {
22+
return fmt.Errorf("failed to run %v: %w (out=%q)", cmd.Args, err, string(out))
23+
}
24+
25+
cmd = exec.Command("codesign", "--display", "--entitlements", "-", "--xml", qExe)
26+
out, err = cmd.CombinedOutput()
27+
logrus.WithError(err).Debugf("Executed %v: out=%q", cmd.Args, string(out))
28+
if err != nil {
29+
return fmt.Errorf("failed to run %v: %w (out=%q)", cmd.Args, err, string(out))
30+
}
31+
if !strings.Contains(string(out), "com.apple.security.hypervisor") {
32+
return fmt.Errorf("binary %q seems signed but lacking the \"com.apple.security.hypervisor\" entitlement", qExe)
33+
}
34+
return nil
35+
}
36+
37+
func Sign(qExe string) error {
38+
ent, err := os.CreateTemp("", "lima-qemu-entitlements-*.xml")
39+
if err != nil {
40+
return fmt.Errorf("failed to create a temporary file for signing QEMU binary: %w", err)
41+
}
42+
entName := ent.Name()
43+
defer os.RemoveAll(entName)
44+
const entXML = `<?xml version="1.0" encoding="UTF-8"?>
45+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
46+
<plist version="1.0">
47+
<dict>
48+
<key>com.apple.security.hypervisor</key>
49+
<true/>
50+
</dict>
51+
</plist>`
52+
if _, err = ent.Write([]byte(entXML)); err != nil {
53+
return fmt.Errorf("Failed to write to a temporary file %q for signing QEMU binary: %w", entName, err)
54+
}
55+
ent.Close()
56+
signCmd := exec.Command("codesign", "--sign", "-", "--entitlements", entName, "--force", qExe)
57+
out, err := signCmd.CombinedOutput()
58+
logrus.WithError(err).Debugf("Executed %v: out=%q", signCmd.Args, string(out))
59+
if err != nil {
60+
return fmt.Errorf("failed to run %v: %w (out=%q)", signCmd.Args, err, string(out))
61+
}
62+
return nil
63+
}
64+
65+
// AskToSignIfNotSignedProperly asks to sign the QEMU binary with the "com.apple.security.hypervisor" entitlement.
66+
//
67+
// On Homebrew, QEMU binaries are usually already signed, but Homebrew's signing infrastructure is broken for Intel as of Augest 2023.
68+
// https://github.com/lima-vm/lima/issues/1742
69+
func AskToSignIfNotSignedProperly(qExe string) {
70+
if isSignedErr := IsSigned(qExe); isSignedErr != nil {
71+
logrus.WithError(isSignedErr).Warnf("QEMU binary %q is not properly signed with the \"com.apple.security.hypervisor\" entitlement", qExe)
72+
var ans bool
73+
if isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd()) {
74+
prompt := &survey.Confirm{
75+
Message: fmt.Sprintf("Try to sign %q with the \"com.apple.security.hypervisor\" entitlement?", qExe),
76+
Default: true,
77+
}
78+
if askErr := survey.AskOne(prompt, &ans); askErr != nil {
79+
logrus.WithError(askErr).Warn("No answer was given")
80+
}
81+
}
82+
if ans {
83+
if signErr := Sign(qExe); signErr != nil {
84+
logrus.WithError(signErr).Warnf("Failed to sign %q", qExe)
85+
} else {
86+
logrus.Infof("Successfully signed %q with the \"com.apple.security.hypervisor\" entitlement", qExe)
87+
}
88+
} else {
89+
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 .")
90+
}
91+
} else {
92+
logrus.Infof("QEMU binary %q seems properly signed with the \"com.apple.security.hypervisor\" entitlement", qExe)
93+
}
94+
}

Diff for: pkg/qemu/qemu.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,7 @@ func qemuMachine(arch limayaml.Arch) string {
469469

470470
func Cmdline(cfg Config) (string, []string, error) {
471471
y := cfg.LimaYAML
472-
exe, args, err := getExe(*y.Arch)
472+
exe, args, err := Exe(*y.Arch)
473473
if err != nil {
474474
return "", nil, err
475475
}
@@ -490,7 +490,7 @@ func Cmdline(cfg Config) (string, []string, error) {
490490
}
491491

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

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

1027-
func getAccel(arch limayaml.Arch) string {
1027+
func Accel(arch limayaml.Arch) string {
10281028
if limayaml.IsNativeArch(arch) {
10291029
switch runtime.GOOS {
10301030
case "darwin":

Diff for: pkg/start/start.go

+15
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@ import (
99
"os"
1010
"os/exec"
1111
"path/filepath"
12+
"runtime"
1213
"text/template"
1314
"time"
1415

1516
"github.com/lima-vm/lima/pkg/driver"
1617
"github.com/lima-vm/lima/pkg/driverutil"
18+
"github.com/lima-vm/lima/pkg/qemu"
19+
"github.com/lima-vm/lima/pkg/qemu/entitlementutil"
1720

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

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

107+
// Ask the user to sign the qemu binary with the "com.apple.security.hypervisor" if needed.
108+
// Workaround for https://github.com/lima-vm/lima/issues/1742
109+
if runtime.GOOS == "darwin" && inst.VMType == limayaml.QEMU {
110+
qExe, _, err := qemu.Exe(inst.Arch)
111+
if err != nil {
112+
return fmt.Errorf("failed to find the QEMU binary for the architecture %q: %w", inst.Arch, err)
113+
}
114+
if accel := qemu.Accel(inst.Arch); accel == "hvf" {
115+
entitlementutil.AskToSignIfNotSignedProperly(qExe)
116+
}
117+
}
118+
104119
prepared, err := Prepare(ctx, inst)
105120
if err != nil {
106121
return err

0 commit comments

Comments
 (0)