Close diagnostic and restore coverage gaps

This commit is contained in:
2026-08-11 12:28:55 +00:00
parent 8ef6e99d69
commit a2a144dffa
4 changed files with 289 additions and 12 deletions

View File

@@ -156,13 +156,17 @@ func setOpenedDirectoryMode(root *os.Root, mode os.FileMode) error {
}
func createSiblingTemp(parent *os.Root, base string) (*os.File, string, error) {
return createSiblingTempWithFlags(parent, base, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o600)
}
func createSiblingTempWithFlags(parent *os.Root, base string, flags int, mode os.FileMode) (*os.File, string, error) {
for attempt := 0; attempt < 100; attempt++ {
var randomBytes [16]byte
if _, err := rand.Read(randomBytes[:]); err != nil {
return nil, "", fmt.Errorf("generate temporary file name: %w", err)
}
name := "." + base + ".tmp-" + hex.EncodeToString(randomBytes[:])
file, err := parent.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o600)
file, err := parent.OpenFile(name, flags|os.O_CREATE|os.O_EXCL, mode)
if errors.Is(err, os.ErrExist) {
continue
}
@@ -184,28 +188,128 @@ func syncOpenedDirectory(parent *os.Root) error {
}
// OpenFileConfined opens a file after verifying its parent hierarchy without
// following symbolic links. Existing symbolic-link and non-regular leaves are
// mutating through symbolic links. Truncating opens atomically replace the leaf
// with a newly created sibling, while other creating opens never carry O_CREATE
// into an existing-leaf open. Existing symbolic-link and non-regular leaves are
// rejected.
func OpenFileConfined(path string, flags int, mode os.FileMode) (*os.File, error) {
return openFileConfined(path, flags, mode, nil)
}
func openFileConfined(path string, flags int, mode os.FileMode, beforeOpen func() error) (*os.File, error) {
parent, name, err := openConfinedParent(path, false, 0)
if err != nil {
return nil, err
}
defer func() { _ = parent.Close() }()
if info, err := parent.Lstat(name); err == nil {
if info.Mode()&os.ModeSymlink != 0 {
return nil, fmt.Errorf("destination file %q is a symbolic link", name)
if flags&(os.O_CREATE|os.O_EXCL) == os.O_CREATE|os.O_EXCL {
if err := runBeforeConfinedOpen(beforeOpen); err != nil {
return nil, err
}
if !info.Mode().IsRegular() {
return nil, fmt.Errorf("destination file %q is not a regular file", name)
file, err := parent.OpenFile(name, flags, mode)
if err != nil {
return nil, err
}
} else if err != nil && !errors.Is(err, os.ErrNotExist) {
return nil, fmt.Errorf("inspect destination file %q: %w", name, err)
return validateOpenedConfinedFile(parent, name, file)
}
file, err := parent.OpenFile(name, flags, mode)
exists, err := validateConfinedLeaf(parent, name)
if err != nil {
return nil, err
}
if flags&os.O_TRUNC != 0 {
if !exists && flags&os.O_CREATE == 0 {
return nil, fmt.Errorf("open destination file %q: %w", name, os.ErrNotExist)
}
return replaceAndOpenConfinedFile(parent, name, flags, mode, beforeOpen)
}
if err := runBeforeConfinedOpen(beforeOpen); err != nil {
return nil, err
}
if !exists && flags&os.O_CREATE != 0 {
file, err := parent.OpenFile(name, flags|os.O_EXCL, mode)
if err == nil {
return validateOpenedConfinedFile(parent, name, file)
}
if !errors.Is(err, os.ErrExist) {
return nil, err
}
if _, validationErr := validateConfinedLeaf(parent, name); validationErr != nil {
return nil, validationErr
}
}
// The leaf now exists, so removing O_CREATE prevents a raced dangling
// symlink from creating its target. Opening an existing symlink can expose a
// handle, but no caller can mutate through it before the identity check below.
file, err := parent.OpenFile(name, flags&^os.O_CREATE, mode)
if err != nil {
return nil, err
}
return validateOpenedConfinedFile(parent, name, file)
}
func validateConfinedLeaf(parent *os.Root, name string) (bool, error) {
info, err := parent.Lstat(name)
if errors.Is(err, os.ErrNotExist) {
return false, nil
}
if err != nil {
return false, fmt.Errorf("inspect destination file %q: %w", name, err)
}
if info.Mode()&os.ModeSymlink != 0 {
return false, fmt.Errorf("destination file %q is a symbolic link", name)
}
if !info.Mode().IsRegular() {
return false, fmt.Errorf("destination file %q is not a regular file", name)
}
return true, nil
}
func replaceAndOpenConfinedFile(parent *os.Root, name string, flags int, mode os.FileMode, beforeOpen func() error) (_ *os.File, resultErr error) {
temporary, temporaryName, err := createSiblingTempWithFlags(parent, name, flags&^os.O_TRUNC, mode)
if err != nil {
return nil, fmt.Errorf("create replacement for destination file %q: %w", name, err)
}
installed := false
defer func() {
if installed {
return
}
closeErr := temporary.Close()
removeErr := parent.Remove(temporaryName)
if errors.Is(removeErr, os.ErrNotExist) {
removeErr = nil
}
resultErr = errors.Join(resultErr, closeErr, removeErr)
}()
if err := runBeforeConfinedOpen(beforeOpen); err != nil {
return nil, err
}
// Rename replaces the directory entry itself rather than following it. A
// leaf changed to a symlink after validation therefore cannot redirect the
// truncation to its target.
if err := parent.Rename(temporaryName, name); err != nil {
return nil, fmt.Errorf("install replacement for destination file %q: %w", name, err)
}
installed = true
return validateOpenedConfinedFile(parent, name, temporary)
}
func runBeforeConfinedOpen(beforeOpen func() error) error {
if beforeOpen == nil {
return nil
}
if err := beforeOpen(); err != nil {
return fmt.Errorf("check before confined file open: %w", err)
}
return nil
}
func validateOpenedConfinedFile(parent *os.Root, name string, file *os.File) (*os.File, error) {
opened, err := file.Stat()
if err != nil {
_ = file.Close()

View File

@@ -54,3 +54,95 @@ func TestOpenFileConfinedRejectsSymlinkWithoutTruncatingTarget(t *testing.T) {
t.Fatalf("target content = %q, want %q", data, original)
}
}
func TestOpenFileConfinedDoesNotFollowLeafReplacedBeforeTruncate(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("creating symlinks requires privileges that are not available on every Windows runner")
}
dir := t.TempDir()
target := filepath.Join(dir, "target.log")
const original = "unrelated content"
if err := os.WriteFile(target, []byte(original), 0o600); err != nil {
t.Fatalf("WriteFile(target) error = %v", err)
}
leaf := filepath.Join(dir, "diagnostic.log")
if err := os.WriteFile(leaf, []byte("old diagnostic"), 0o600); err != nil {
t.Fatalf("WriteFile(leaf) error = %v", err)
}
file, err := openFileConfined(leaf, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, WorkspaceFileMode, func() error {
if err := os.Remove(leaf); err != nil {
return err
}
return os.Symlink(filepath.Base(target), leaf)
})
if err != nil {
t.Fatalf("openFileConfined() error = %v", err)
}
if _, err := file.WriteString("new diagnostic"); err != nil {
_ = file.Close()
t.Fatalf("WriteString() error = %v", err)
}
if err := file.Close(); err != nil {
t.Fatalf("Close() error = %v", err)
}
targetData, err := os.ReadFile(target)
if err != nil {
t.Fatalf("ReadFile(target) error = %v", err)
}
if string(targetData) != original {
t.Fatalf("target content = %q, want %q", targetData, original)
}
leafData, err := os.ReadFile(leaf)
if err != nil {
t.Fatalf("ReadFile(leaf) error = %v", err)
}
if string(leafData) != "new diagnostic" {
t.Fatalf("leaf content = %q, want replacement diagnostic", leafData)
}
info, err := os.Lstat(leaf)
if err != nil {
t.Fatalf("Lstat(leaf) error = %v", err)
}
if info.Mode()&os.ModeSymlink != 0 || !info.Mode().IsRegular() {
t.Fatalf("leaf mode = %v, want regular file", info.Mode())
}
}
func TestOpenFileConfinedRejectsLeafReplacedBeforeExistingFileOpen(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("creating symlinks requires privileges that are not available on every Windows runner")
}
dir := t.TempDir()
target := filepath.Join(dir, "target.lock")
const original = "unrelated lock content"
if err := os.WriteFile(target, []byte(original), 0o600); err != nil {
t.Fatalf("WriteFile(target) error = %v", err)
}
leaf := filepath.Join(dir, "session.lock")
if err := os.WriteFile(leaf, []byte("old lock"), 0o600); err != nil {
t.Fatalf("WriteFile(leaf) error = %v", err)
}
file, err := openFileConfined(leaf, os.O_RDWR|os.O_CREATE, WorkspaceFileMode, func() error {
if err := os.Remove(leaf); err != nil {
return err
}
return os.Symlink(filepath.Base(target), leaf)
})
if file != nil {
_ = file.Close()
t.Fatal("openFileConfined() returned a raced symbolic-link target")
}
if err == nil || !strings.Contains(err.Error(), "changed while being opened") {
t.Fatalf("openFileConfined() error = %v, want identity-change rejection", err)
}
targetData, readErr := os.ReadFile(target)
if readErr != nil {
t.Fatalf("ReadFile(target) error = %v", readErr)
}
if string(targetData) != original {
t.Fatalf("target content = %q, want %q", targetData, original)
}
}