fix(vm): recognise the Firecracker the jailer execs into its chroot
The jailer starts Firecracker as '/firecracker --id <id> --start-time-us ...':
no --api-sock and no --exec-file on its command line. The /proc scan used by
Stop and Reap only knew those two markers, so a jailed VMM was invisible: it
survived a kill-by-id, left its cgroup busy ('remove cgroup: device or
resource busy') and Reap could not find it. Accept --start-time-us (the
jailer's own argument) as the marker too, in the driver and in the leak check
of the integration tests.- commit
- 2bb0741d311eccfe139550d653858c5993c2f38c
- author
- brice · · 2026-10-12 21:27 UTC
- parent
- ea344ad
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +15 -11
M internal/sandbox/integration/integration_test.go +2 -2
| @@ -214,7 +214,7 @@ func leftovers(stateDir string) []string { | |||
| 214 | 214 | } | |
| 215 | 215 | ||
| 216 | 216 | // vmmProcs returns the processes whose command line carries one of our VM ids | |
| 217 | - | // (and --api-sock or --exec-file), whatever the program is called: the rule the | |
| 217 | + | // (and --api-sock, --exec-file or the jailer's --start-time-us), whatever the program is called: the rule the | |
| 218 | 218 | // driver's Reap uses. | |
| 219 | 219 | func vmmProcs() map[int]string { | |
| 220 | 220 | out := map[int]string{} | |
| @@ -233,7 +233,7 @@ func vmmProcs() map[int]string { | |||
| 233 | 233 | if i+1 < len(args) && sandbox.ValidID(args[i+1]) { | |
| 234 | 234 | id = args[i+1] | |
| 235 | 235 | } | |
| 236 | - | case "--api-sock", "--exec-file": | |
| 236 | + | case "--api-sock", "--exec-file", "--start-time-us": | |
| 237 | 237 | marker = true | |
| 238 | 238 | } | |
| 239 | 239 | } | |
| @@ -214,7 +214,7 @@ func leftovers(stateDir string) []string { | |||||
| 214 | } | 214 | } | ||
| 215 | 215 | ||||
| 216 | // vmmProcs returns the processes whose command line carries one of our VM ids | 216 | // vmmProcs returns the processes whose command line carries one of our VM ids | ||
| 217 | - | // (and --api-sock or --exec-file), whatever the program is called: the rule the | 217 | + | // (and --api-sock, --exec-file or the jailer's --start-time-us), whatever the program is called: the rule the |
| 218 | // driver's Reap uses. | 218 | // driver's Reap uses. | ||
| 219 | func vmmProcs() map[int]string { | 219 | func vmmProcs() map[int]string { | ||
| 220 | out := map[int]string{} | 220 | out := map[int]string{} | ||
| @@ -233,7 +233,7 @@ func vmmProcs() map[int]string { | |||||
| 233 | if i+1 < len(args) && sandbox.ValidID(args[i+1]) { | 233 | if i+1 < len(args) && sandbox.ValidID(args[i+1]) { | ||
| 234 | id = args[i+1] | 234 | id = args[i+1] | ||
| 235 | } | 235 | } | ||
| 236 | - | case "--api-sock", "--exec-file": | 236 | + | case "--api-sock", "--exec-file", "--start-time-us": |
| 237 | marker = true | 237 | marker = true | ||
| 238 | } | 238 | } | ||
| 239 | } | 239 | } | ||
M internal/sandbox/vm/proc.go +4 -2
| @@ -18,7 +18,9 @@ import ( | |||
| 18 | 18 | // vmmProcs scans a /proc tree for processes started by this runner for a VM: | |
| 19 | 19 | // the Firecracker process and the jailer. A process is ours when its command | |
| 20 | 20 | // line has "--id <id>" with an id of the form sandbox.NewID produces and also | |
| 21 | - | // "--api-sock" (Firecracker) or "--exec-file" (jailer). The program name is not | |
| 21 | + | // "--api-sock" (Firecracker), "--exec-file" (jailer) or "--start-time-us" (the | |
| 22 | + | // Firecracker the jailer execs into its chroot: it gets neither of the first two, | |
| 23 | + | // only --id and the jailer's --start-time-* arguments). The program name is not | |
| 22 | 24 | // looked at: the binary may be renamed or versioned (firecracker-v1.17), and a | |
| 23 | 25 | // jailer in a PID namespace leaves a Firecracker whose argv[0] is whatever the | |
| 24 | 26 | // jailer chose. Processes of other users of Firecracker (other ids) are never | |
| @@ -55,7 +57,7 @@ func vmIDFromCmdline(args []string) string { | |||
| 55 | 57 | if i+1 < len(args) && sandbox.ValidID(args[i+1]) { | |
| 56 | 58 | id = args[i+1] | |
| 57 | 59 | } | |
| 58 | - | case "--api-sock", "--exec-file": | |
| 60 | + | case "--api-sock", "--exec-file", "--start-time-us": | |
| 59 | 61 | marker = true | |
| 60 | 62 | } | |
| 61 | 63 | } | |
| @@ -18,7 +18,9 @@ import ( | |||||
| 18 | // vmmProcs scans a /proc tree for processes started by this runner for a VM: | 18 | // vmmProcs scans a /proc tree for processes started by this runner for a VM: | ||
| 19 | // the Firecracker process and the jailer. A process is ours when its command | 19 | // the Firecracker process and the jailer. A process is ours when its command | ||
| 20 | // line has "--id <id>" with an id of the form sandbox.NewID produces and also | 20 | // line has "--id <id>" with an id of the form sandbox.NewID produces and also | ||
| 21 | - | // "--api-sock" (Firecracker) or "--exec-file" (jailer). The program name is not | 21 | + | // "--api-sock" (Firecracker), "--exec-file" (jailer) or "--start-time-us" (the |
| 22 | + | // Firecracker the jailer execs into its chroot: it gets neither of the first two, | |||
| 23 | + | // only --id and the jailer's --start-time-* arguments). The program name is not | |||
| 22 | // looked at: the binary may be renamed or versioned (firecracker-v1.17), and a | 24 | // looked at: the binary may be renamed or versioned (firecracker-v1.17), and a | ||
| 23 | // jailer in a PID namespace leaves a Firecracker whose argv[0] is whatever the | 25 | // jailer in a PID namespace leaves a Firecracker whose argv[0] is whatever the | ||
| 24 | // jailer chose. Processes of other users of Firecracker (other ids) are never | 26 | // jailer chose. Processes of other users of Firecracker (other ids) are never | ||
| @@ -55,7 +57,7 @@ func vmIDFromCmdline(args []string) string { | |||||
| 55 | if i+1 < len(args) && sandbox.ValidID(args[i+1]) { | 57 | if i+1 < len(args) && sandbox.ValidID(args[i+1]) { | ||
| 56 | id = args[i+1] | 58 | id = args[i+1] | ||
| 57 | } | 59 | } | ||
| 58 | - | case "--api-sock", "--exec-file": | 60 | + | case "--api-sock", "--exec-file", "--start-time-us": |
| 59 | marker = true | 61 | marker = true | ||
| 60 | } | 62 | } | ||
| 61 | } | 63 | } | ||
M internal/sandbox/vm/vm_test.go +9 -7
| @@ -724,13 +724,15 @@ func TestVMIDFromCmdline(t *testing.T) { | |||
| 724 | 724 | args []string | |
| 725 | 725 | want string | |
| 726 | 726 | }{ | |
| 727 | - | "firecracker": {[]string{"firecracker", "--id", "er00112233", "--api-sock", "/x"}, "er00112233"}, | |
| 728 | - | "jailer": {[]string{"jailer", "--id", "er00112233", "--exec-file", "/fc", "--uid", "1"}, "er00112233"}, | |
| 729 | - | "renamed": {[]string{"anything", "--api-sock", "/x", "--id", "er44556677"}, "er44556677"}, | |
| 730 | - | "foreign id": {[]string{"firecracker", "--id", "my-vm", "--api-sock", "/x"}, ""}, | |
| 731 | - | "no marker": {[]string{"python", "--id", "er00112233"}, ""}, | |
| 732 | - | "id without value": {[]string{"firecracker", "--api-sock", "/x", "--id"}, ""}, | |
| 733 | - | "path-like id": {[]string{"firecracker", "--id", "er../../../x", "--api-sock", "/x"}, ""}, | |
| 727 | + | "firecracker": {[]string{"firecracker", "--id", "er00112233", "--api-sock", "/x"}, "er00112233"}, | |
| 728 | + | "jailer": {[]string{"jailer", "--id", "er00112233", "--exec-file", "/fc", "--uid", "1"}, "er00112233"}, | |
| 729 | + | "renamed": {[]string{"anything", "--api-sock", "/x", "--id", "er44556677"}, "er44556677"}, | |
| 730 | + | // What a real jailer 1.17 leaves: /firecracker --id <id> --start-time-us ... | |
| 731 | + | "jailed firecracker": {[]string{"/firecracker", "--id", "er00112233", "--start-time-us", "150801025577", "--start-time-cpu-us", "60", "--parent-cpu-time-us", "1332"}, "er00112233"}, | |
| 732 | + | "foreign id": {[]string{"firecracker", "--id", "my-vm", "--api-sock", "/x"}, ""}, | |
| 733 | + | "no marker": {[]string{"python", "--id", "er00112233"}, ""}, | |
| 734 | + | "id without value": {[]string{"firecracker", "--api-sock", "/x", "--id"}, ""}, | |
| 735 | + | "path-like id": {[]string{"firecracker", "--id", "er../../../x", "--api-sock", "/x"}, ""}, | |
| 734 | 736 | } { | |
| 735 | 737 | if got := vmIDFromCmdline(tc.args); got != tc.want { | |
| 736 | 738 | t.Errorf("%s: got %q want %q", name, got, tc.want) | |
| @@ -724,13 +724,15 @@ func TestVMIDFromCmdline(t *testing.T) { | |||||
| 724 | args []string | 724 | args []string | ||
| 725 | want string | 725 | want string | ||
| 726 | }{ | 726 | }{ | ||
| 727 | - | "firecracker": {[]string{"firecracker", "--id", "er00112233", "--api-sock", "/x"}, "er00112233"}, | 727 | + | "firecracker": {[]string{"firecracker", "--id", "er00112233", "--api-sock", "/x"}, "er00112233"}, |
| 728 | - | "jailer": {[]string{"jailer", "--id", "er00112233", "--exec-file", "/fc", "--uid", "1"}, "er00112233"}, | 728 | + | "jailer": {[]string{"jailer", "--id", "er00112233", "--exec-file", "/fc", "--uid", "1"}, "er00112233"}, |
| 729 | - | "renamed": {[]string{"anything", "--api-sock", "/x", "--id", "er44556677"}, "er44556677"}, | 729 | + | "renamed": {[]string{"anything", "--api-sock", "/x", "--id", "er44556677"}, "er44556677"}, |
| 730 | - | "foreign id": {[]string{"firecracker", "--id", "my-vm", "--api-sock", "/x"}, ""}, | 730 | + | // What a real jailer 1.17 leaves: /firecracker --id <id> --start-time-us ... |
| 731 | - | "no marker": {[]string{"python", "--id", "er00112233"}, ""}, | 731 | + | "jailed firecracker": {[]string{"/firecracker", "--id", "er00112233", "--start-time-us", "150801025577", "--start-time-cpu-us", "60", "--parent-cpu-time-us", "1332"}, "er00112233"}, |
| 732 | - | "id without value": {[]string{"firecracker", "--api-sock", "/x", "--id"}, ""}, | 732 | + | "foreign id": {[]string{"firecracker", "--id", "my-vm", "--api-sock", "/x"}, ""}, |
| 733 | - | "path-like id": {[]string{"firecracker", "--id", "er../../../x", "--api-sock", "/x"}, ""}, | 733 | + | "no marker": {[]string{"python", "--id", "er00112233"}, ""}, |
| 734 | + | "id without value": {[]string{"firecracker", "--api-sock", "/x", "--id"}, ""}, | |||
| 735 | + | "path-like id": {[]string{"firecracker", "--id", "er../../../x", "--api-sock", "/x"}, ""}, | |||
| 734 | } { | 736 | } { | ||
| 735 | if got := vmIDFromCmdline(tc.args); got != tc.want { | 737 | if got := vmIDFromCmdline(tc.args); got != tc.want { | ||
| 736 | t.Errorf("%s: got %q want %q", name, got, tc.want) | 738 | t.Errorf("%s: got %q want %q", name, got, tc.want) | ||