hostfiles: removePastCap refuses a final name that is not plain ("", ".", ".."), as descendCtx refuses such a directory component (census ruling condition 2, platform-1e)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 files changed,
+22
−0
internal/hostfiles/fix5_linux_test.go+16 −0internal/hostfiles/restore_unix.go+6 −0
diff --git a/internal/hostfiles/fix5_linux_test.go b/internal/hostfiles/fix5_linux_test.go| index 8a9824237..a394664a6 100644 |
| --- a/internal/hostfiles/fix5_linux_test.go |
| +++ b/internal/hostfiles/fix5_linux_test.go |
| @@ -433,3 +433,19 @@ func TestRestoreNeverFollowsASymlinkOnADeepTombstone(t *testing.T) { |
| }) |
| } |
| } |
| + |
| +// The census ruling for removePastCap (platform-1e, #236) holds it to a |
| +// plain final name, as descendCtx holds each directory component: an |
| +// empty, "." or ".." final name refuses, and nothing is removed. |
| +func TestRemovePastCapRefusesANameThatIsNotPlain(t *testing.T) { |
| + r, err := os.OpenRoot(t.TempDir()) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + defer r.Close() |
| + for _, rel := range []string{"a/", "a/.", "a/..", ".."} { |
| + if removed, err := removePastCap(context.Background(), r, rel); err == nil || removed { |
| + t.Fatalf("removePastCap(%q): %v, removed %v", rel, err, removed) |
| + } |
| + } |
| +} |
diff --git a/internal/hostfiles/restore_unix.go b/internal/hostfiles/restore_unix.go| index 5bff6bd08..8f935e1d4 100644 |
| --- a/internal/hostfiles/restore_unix.go |
| +++ b/internal/hostfiles/restore_unix.go |
| @@ -7,6 +7,7 @@ import ( |
| "errors" |
| "os" |
| "path" |
| + "strconv" |
| "strings" |
| "golang.org/x/sys/unix" |
| @@ -43,6 +44,11 @@ func removePastCap(ctx context.Context, root *os.Root, rel string) (bool, error) |
| return false, err |
| } |
| parent, name := path.Split(rel) |
| + if name == "" || name == "." || name == ".." { |
| + // descendCtx refuses such a directory component; the final name is |
| + // held to the same rule, so only a plain name is ever unlinked. |
| + return false, errors.New("hostfiles: path component " + strconv.Quote(name) + " in " + strconv.Quote(rel) + " is not a plain name") |
| + } |
| dir, gone, err := top.descendCtx(ctx, strings.TrimSuffix(parent, "/")) |
| if err != nil || gone { |
| return false, err |