Skip to content

Windows: rename-overwrite of an open watched file fails with STATUS_ACCESS_DENIED #172

@arr2036

Description

@arr2036

Summary

test_kevent_vnode_rename_overwrite_ordering is gated off Win32 in test/vnode.c because both MoveFileExA(MOVEFILE_REPLACE_EXISTING) and the kernel-level SetFileInformationByHandle(FileRenameInfo, ReplaceIfExists=TRUE) return STATUS_ACCESS_DENIED (Win32 error 5) on the test's setup, despite every relevant open being made with FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE and the source HANDLE having DELETE access for the SetFileInfo path.

Setup

testfile_create(src_path);                       // creates files (no leftover handles)
testfile_create(tgt_path);
src_fd = open(src_path, O_RDWR);                 // shimmed: FILE_SHARE_DELETE
tgt_fd = open(tgt_path, O_RDWR);                 // shimmed: FILE_SHARE_DELETE
kevent_add(src_fd, EVFILT_VNODE, NOTE_RENAME);   // FindFirstChangeNotificationW on parent dir
kevent_add(tgt_fd, EVFILT_VNODE, NOTE_DELETE);   // FindFirstChangeNotificationW on parent dir
rename(src_path, tgt_path);                      // FAILS gle=5

Both the user-mode wrapper (MoveFileExA) and the NT direct path (NtSetInformationFile/SetFileInformationByHandle via a fresh CreateFileA(src, DELETE|SYNCHRONIZE, FILE_SHARE_*, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS) handle) return ERROR_ACCESS_DENIED. Verified empirically across Release / Debug / Debug+ASAN.

What works

  • test_kevent_vnode_note_rename (rename to a non-existing target) passes - same temp dir, same kqueue, same vnode filter machinery, just no REPLACE_EXISTING.
  • Earlier vnode tests pass with the same FindFirstChangeNotificationW handles open on the temp dir.

So the fail mode is specifically the overwrite path: MoveFileEx (and the kernel rename) wants to delete the existing target before atomically swapping the source's name in. Some interlock is rejecting the delete despite the target's open handles all having FILE_SHARE_DELETE.

Hypotheses (unverified)

  1. FindFirstChangeNotificationW opens the parent directory with a share mode that, in combination with the test's per-file watchers (two of them on the same dir), denies the delete-and-rename through the kernel rename API.
  2. Defender / EDR is incidentally holding a transient handle on the freshly-created target.
  3. Some Win32-specific access check on REPLACE_EXISTING requires the current process to own the destination via more than just FILE_SHARE_DELETE (e.g., the deletion path may want DELETE access on every existing handle, which our shimmed open doesn't request).

Pinpointing requires a minimal repro outside the test harness: open a file with FILE_SHARE_DELETE, set up FindFirstChangeNotificationW on its parent, then call MoveFileExA(REPLACE_EXISTING) from another path. None of this is in scope for the Windows port itself; tracking here so it doesn't get lost.

Files

  • Test gated: test/vnode.c:1226-1232 (#if defined(NOTE_RENAME) && !defined(_WIN32))
  • Rename shim with NT-direct fallback: test/win32_compat.h (kq_rename_via_setinfo)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions