fix(vm): create the jailer chroot base directory
Jailer 1.17 canonicalizes --chroot-base-dir and exits with 'Failed to canonicalize path' when it does not exist, so every jailed VM failed to boot on a fresh state directory. Create <state>/jail (0700) at driver start. Found running the VM integration tests with the jailer as root.
- commit
- ed21f4fd1e3a994feba33bbdd2899648e63ec823
- author
- brice · · 2026-10-12 21:09 UTC
- parent
- e917abe
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +22 -0
M internal/sandbox/vm/driver.go +5 -0
| @@ -121,6 +121,11 @@ func New(ctx context.Context, cfg sandbox.Config, opt Options) (*Driver, error) | |||
| 121 | 121 | fail := func(err error) (*Driver, error) { _ = d.Close(); return nil, err } | |
| 122 | 122 | ||
| 123 | 123 | if !cfg.JailerOff { | |
| 124 | + | // Jailer 1.17 canonicalizes --chroot-base-dir and fails when it does not | |
| 125 | + | // exist ("Failed to canonicalize path"); older releases created it. | |
| 126 | + | if err := os.MkdirAll(jailBase(cfg.StateDir), 0o700); err != nil { | |
| 127 | + | return fail(fmt.Errorf("jailer chroot base: %w", err)) | |
| 128 | + | } | |
| 124 | 129 | for _, p := range []string{cfg.KernelPath(), cfg.BaseImagePath()} { | |
| 125 | 130 | if err := checkWorldReadable(p); err != nil { | |
| 126 | 131 | return fail(err) | |
| @@ -121,6 +121,11 @@ func New(ctx context.Context, cfg sandbox.Config, opt Options) (*Driver, error) | |||||
| 121 | fail := func(err error) (*Driver, error) { _ = d.Close(); return nil, err } | 121 | fail := func(err error) (*Driver, error) { _ = d.Close(); return nil, err } | ||
| 122 | 122 | ||||
| 123 | if !cfg.JailerOff { | 123 | if !cfg.JailerOff { | ||
| 124 | + | // Jailer 1.17 canonicalizes --chroot-base-dir and fails when it does not | |||
| 125 | + | // exist ("Failed to canonicalize path"); older releases created it. | |||
| 126 | + | if err := os.MkdirAll(jailBase(cfg.StateDir), 0o700); err != nil { | |||
| 127 | + | return fail(fmt.Errorf("jailer chroot base: %w", err)) | |||
| 128 | + | } | |||
| 124 | for _, p := range []string{cfg.KernelPath(), cfg.BaseImagePath()} { | 129 | for _, p := range []string{cfg.KernelPath(), cfg.BaseImagePath()} { | ||
| 125 | if err := checkWorldReadable(p); err != nil { | 130 | if err := checkWorldReadable(p); err != nil { | ||
| 126 | return fail(err) | 131 | return fail(err) | ||
M internal/sandbox/vm/vm_test.go +17 -0
| @@ -1022,3 +1022,20 @@ func TestRemoveCgroup(t *testing.T) { | |||
| 1022 | 1022 | t.Fatal("a non-empty cgroup cannot be removed and must be reported") | |
| 1023 | 1023 | } | |
| 1024 | 1024 | } | |
| 1025 | + | ||
| 1026 | + | // Jailer 1.17 canonicalizes --chroot-base-dir and exits when it does not exist: | |
| 1027 | + | // the driver must create it (seen on a real host, invisible with the jailer off). | |
| 1028 | + | func TestNewCreatesTheJailerChrootBase(t *testing.T) { | |
| 1029 | + | e := newEnv(t, "ok") | |
| 1030 | + | e.cfg.JailerOff = false | |
| 1031 | + | e.cfg.Jailer = e.cfg.Firecracker // any executable: it is only resolved here | |
| 1032 | + | d, err := New(context.Background(), e.cfg, Options{Run: e.run, Net: e.plumb, BootTimeout: 5 * time.Second}) | |
| 1033 | + | if err != nil { | |
| 1034 | + | t.Fatal(err) | |
| 1035 | + | } | |
| 1036 | + | defer func() { _ = d.Close() }() | |
| 1037 | + | st, err := os.Stat(jailBase(e.cfg.StateDir)) | |
| 1038 | + | if err != nil || !st.IsDir() { | |
| 1039 | + | t.Fatalf("jail base not created: %v", err) | |
| 1040 | + | } | |
| 1041 | + | } | |
| @@ -1022,3 +1022,20 @@ func TestRemoveCgroup(t *testing.T) { | |||||
| 1022 | t.Fatal("a non-empty cgroup cannot be removed and must be reported") | 1022 | t.Fatal("a non-empty cgroup cannot be removed and must be reported") | ||
| 1023 | } | 1023 | } | ||
| 1024 | } | 1024 | } | ||
| 1025 | + | ||||
| 1026 | + | // Jailer 1.17 canonicalizes --chroot-base-dir and exits when it does not exist: | |||
| 1027 | + | // the driver must create it (seen on a real host, invisible with the jailer off). | |||
| 1028 | + | func TestNewCreatesTheJailerChrootBase(t *testing.T) { | |||
| 1029 | + | e := newEnv(t, "ok") | |||
| 1030 | + | e.cfg.JailerOff = false | |||
| 1031 | + | e.cfg.Jailer = e.cfg.Firecracker // any executable: it is only resolved here | |||
| 1032 | + | d, err := New(context.Background(), e.cfg, Options{Run: e.run, Net: e.plumb, BootTimeout: 5 * time.Second}) | |||
| 1033 | + | if err != nil { | |||
| 1034 | + | t.Fatal(err) | |||
| 1035 | + | } | |||
| 1036 | + | defer func() { _ = d.Close() }() | |||
| 1037 | + | st, err := os.Stat(jailBase(e.cfg.StateDir)) | |||
| 1038 | + | if err != nil || !st.IsDir() { | |||
| 1039 | + | t.Fatalf("jail base not created: %v", err) | |||
| 1040 | + | } | |||
| 1041 | + | } | |||