//go:build linux package host import ( "bytes" "errors" "log" "net" "os" "path/filepath" "strings" "testing" "golang.org/x/sys/unix" ) func TestBootstrapRegistryPlantedLinks(t *testing.T) { for _, dangling := range []bool{false, true} { t.Run(map[bool]string{false: "existing", true: "dangling"}[dangling], func(t *testing.T) { root := t.TempDir() victim := filepath.Join(t.TempDir(), "victim") if !dangling { if err := os.WriteFile(victim, []byte("preserve"), 0o600); err != nil { t.Fatal(err) } } if err := os.Mkdir(filepath.Join(root, "data"), 0o750); err != nil { t.Fatal(err) } if err := os.Symlink(victim, filepath.Join(root, "data", "registry.db")); err != nil { t.Fatal(err) } if err := bootstrap(root, nil); err != nil { t.Fatal(err) } got, err := os.ReadFile(victim) if dangling { if !errors.Is(err, os.ErrNotExist) { t.Fatalf("dangling target created: %v", err) } } else if err != nil || string(got) != "preserve" { t.Fatalf("victim changed: %q %v", got, err) } }) } } func TestBootstrapRefusesWritableParentSymlink(t *testing.T) { for _, parent := range []string{"data", "data/.instances", "run/carlos"} { t.Run(strings.ReplaceAll(parent, "/", "_"), func(t *testing.T) { root, victim := t.TempDir(), t.TempDir() if err := os.Chmod(victim, 0o711); err != nil { t.Fatal(err) } path := filepath.Join(root, parent) if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Fatal(err) } if err := os.Symlink(victim, path); err != nil { t.Fatal(err) } if err := bootstrap(root, nil); err == nil { t.Fatal("accepted parent symlink") } st, err := os.Stat(victim) if err != nil || st.Mode().Perm() != 0o711 { t.Fatalf("victim mode changed: %v %v", st, err) } entries, err := os.ReadDir(victim) if err != nil || len(entries) != 0 { t.Fatalf("victim populated: %v %v", entries, err) } }) } } func TestBootstrapOwnershipWalkSkipsUnsafeEntries(t *testing.T) { dir := t.TempDir() victim := filepath.Join(t.TempDir(), "victim") if err := os.WriteFile(victim, []byte("preserve"), 0o600); err != nil { t.Fatal(err) } if err := os.Chmod(victim, 0o750|os.ModeSetuid|os.ModeSetgid); err != nil { t.Fatal(err) } before, err := os.Stat(victim) if err != nil { t.Fatal(err) } for _, name := range []string{"registry.db-wal", "registry.db-shm"} { if err := os.Link(victim, filepath.Join(dir, name)); err != nil { t.Fatal(err) } } if err := os.Symlink(victim, filepath.Join(dir, "registry.db")); err != nil { t.Fatal(err) } if err := unix.Mkfifo(filepath.Join(dir, "fifo"), 0o600); err != nil { t.Fatal(err) } socket, err := net.Listen("unix", filepath.Join(dir, "socket")) if err != nil { t.Fatal(err) } defer socket.Close() if err := os.Mkdir(filepath.Join(dir, "nested"), 0o750); err != nil { t.Fatal(err) } plain := filepath.Join(dir, "nested", "plain") if err := os.WriteFile(plain, []byte("ordinary"), 0o640); err != nil { t.Fatal(err) } var logs bytes.Buffer old := log.Writer() log.SetOutput(&logs) defer log.SetOutput(old) if err := bootstrapChownTree(dir, os.Getuid(), os.Getgid()); err != nil { t.Fatal(err) } after, err := os.Stat(victim) if err != nil { t.Fatal(err) } if after.Mode() != before.Mode() { t.Fatalf("hard-linked victim ownership operation cleared mode: %v -> %v", before.Mode(), after.Mode()) } got, err := os.ReadFile(victim) if err != nil || string(got) != "preserve" { t.Fatalf("victim changed: %q %v", got, err) } for _, name := range []string{"registry.db", "registry.db-wal", "registry.db-shm", "fifo", "socket"} { if !strings.Contains(logs.String(), filepath.Join(dir, name)) { t.Errorf("missing alert for %s: %s", name, logs.String()) } } got, err = os.ReadFile(plain) if err != nil || string(got) != "ordinary" { t.Fatalf("plain file changed: %q %v", got, err) } } func TestBootstrapOwnershipWalkDoesNotCrossBindMount(t *testing.T) { if os.Geteuid() != 0 { t.Skip("bind mount test requires root with CAP_SYS_ADMIN") } dir, source := t.TempDir(), t.TempDir() target := filepath.Join(dir, "bind") if err := os.Mkdir(target, 0o750); err != nil { t.Fatal(err) } if err := os.Chmod(source, 0o750|os.ModeSetgid); err != nil { t.Fatal(err) } if err := unix.Mount(source, target, "", unix.MS_BIND, ""); err != nil { if errors.Is(err, unix.EPERM) || errors.Is(err, unix.EACCES) { t.Skipf("bind mount test requires CAP_SYS_ADMIN: %v", err) } t.Fatal(err) } defer unix.Unmount(target, 0) before, err := os.Stat(source) if err != nil { t.Fatal(err) } if err := bootstrapChownTree(dir, 65534, 65534); err != nil { t.Fatal(err) } after, err := os.Stat(source) if err != nil { t.Fatal(err) } var sourceStat unix.Stat_t if err := unix.Stat(source, &sourceStat); err != nil { t.Fatal(err) } if before.Mode() != after.Mode() || sourceStat.Uid != uint32(os.Getuid()) { t.Fatalf("crossed bind mount: mode %v -> %v, uid %d", before.Mode(), after.Mode(), sourceStat.Uid) } }