Skip to content

fix(windows): re-stamp the runtime DACL only when it is actually wrong - #1634

Draft
DeusData wants to merge 3 commits into
mainfrom
fix/conditional-dacl-restamp
Draft

fix(windows): re-stamp the runtime DACL only when it is actually wrong#1634
DeusData wants to merge 3 commits into
mainfrom
fix/conditional-dacl-restamp

Conversation

@DeusData

Copy link
Copy Markdown
Owner

fix(windows): re-stamp the runtime DACL only when it is actually wrong

win_runtime_directory_secure called set_security_info with
PROTECTED_DACL_SECURITY_INFORMATION on every process start, whether or not
anything needed repairing. Line 4126 computes created, but it only guards the
ERROR_ALREADY_EXISTS check - the re-stamp itself ran unconditionally.

Two costs, both observed rather than theorised:

#1601 counted ELEVEN "Security change" USN records against a single _config.db
in one day. Windows propagates a directory's security descriptor to its
children, so a rewrite that changes nothing still churns every file underneath.

#1620 loses its atomic publish to this. MoveFileEx needs DELETE on the
destination, and a concurrent re-protect of the parent is a window in which it
can be refused - for a state that was about to be correct anyway. That reporter
proved the interaction by running a background icacls loop during indexing and
watching the identical index succeed.

The repair is what matters, not the ritual. When the owner is already the exact
current user AND the DACL already passes the private-directory check, there is
nothing to fix and the right action is to leave it alone. When it IS wrong, the
repair is byte-for-byte what it was before.

This does not fix the underlying ACL damage on already-broken installs - files
created under the pre-v0.10.3 flagless regime still carry empty DACLs and need
a child repair, which is a separate change. It stops us from making it worse and
from creating a failure window on every start.

Costs one extra GetSecurityInfo to avoid a SetSecurityInfo, which is the cheap
direction.

Builds clean; daemon_ipc 48 passed. The changed region is Windows-only, so the
real verification is the Windows CI leg.

win_runtime_directory_secure called set_security_info with
PROTECTED_DACL_SECURITY_INFORMATION on every process start, whether or not
anything needed repairing. Line 4126 computes `created`, but it only guards the
ERROR_ALREADY_EXISTS check - the re-stamp itself ran unconditionally.

Two costs, both observed rather than theorised:

#1601 counted ELEVEN "Security change" USN records against a single _config.db
in one day. Windows propagates a directory's security descriptor to its
children, so a rewrite that changes nothing still churns every file underneath.

#1620 loses its atomic publish to this. MoveFileEx needs DELETE on the
destination, and a concurrent re-protect of the parent is a window in which it
can be refused - for a state that was about to be correct anyway. That reporter
proved the interaction by running a background icacls loop during indexing and
watching the identical index succeed.

The repair is what matters, not the ritual. When the owner is already the exact
current user AND the DACL already passes the private-directory check, there is
nothing to fix and the right action is to leave it alone. When it IS wrong, the
repair is byte-for-byte what it was before.

This does not fix the underlying ACL damage on already-broken installs - files
created under the pre-v0.10.3 flagless regime still carry empty DACLs and need
a child repair, which is a separate change. It stops us from making it worse and
from creating a failure window on every start.

Costs one extra GetSecurityInfo to avoid a SetSecurityInfo, which is the cheap
direction.

Builds clean; daemon_ipc 48 passed. The changed region is Windows-only, so the
real verification is the Windows CI leg.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The previous commit called win_file_security_secure with five arguments; on this
branch it takes four. That is a compile error on Windows.

It passed the local build because the entire win_* region is Windows-only and
guarded out on macOS, so a clean macOS build says nothing about it. Worth
recording as the trap it is: for Windows-only code, a green host build is not
evidence, and the Windows CI leg is the first thing that can actually reject it.

(The five-argument form belongs to #1623, which threads an ancestor flag through
this function. When that lands, this call becomes the five-argument form again on
rebase.)

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
…ignature

#1623 landed on main and threads an ancestor flag through
win_file_security_secure. The conditional check added here predates it and still
passed four arguments; both calls now pass ancestor=false, which is correct - the
private runtime directory is never an ancestor and must keep full strictness.

This is the second arity correction on this branch, in opposite directions, and
the reason is worth stating: the win_* region is Windows-only, so a local macOS
build compiles none of it and reports success regardless. Nothing on this machine
can catch a mismatched call here. The Windows CI leg is the only venue that can.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
@DeusData
DeusData force-pushed the fix/conditional-dacl-restamp branch from 0fe264f to dfd9f7e Compare August 14, 2026 14:24
@DeusData
DeusData marked this pull request as draft August 14, 2026 18:20
@DeusData

Copy link
Copy Markdown
Owner Author

Converting to draft — this is a real regression on Windows, not a flake.

test-windows 1/2 and 2/2 fail with ASSERT(locks_ready) in test_daemon_application.c and ASSERT(frontend…) in test_daemon_frontend.c — the daemon cannot establish its runtime directory, which is exactly the code this PR changes.

The flaw is in my already_correct gate. It tests whether the DACL currently carries no untrusted mutation grants. But the unconditional call it replaces passed PROTECTED_DACL_SECURITY_INFORMATION, which disables inheritance. Those are not the same property: a freshly created directory can look clean at that instant while still being open to inherited ACEs, so skipping the stamp leaves it unprotected and the later strict validation refuses it.

I optimised away a call whose purpose I had only partly understood. The re-stamp is not merely repair — it is also what makes the DACL protected in the first place.

The underlying complaint is still real (#1601 counted eleven Security change USN records in a day, and #1620 loses an atomic publish to the rewrite window), so the right version of this checks protectedness as well as grant-cleanliness before skipping — or skips only when the descriptor is already both protected and correct. That needs the Windows leg to verify, since none of it compiles on macOS.

#1636 depends on this and goes to draft with it. Neither will be in v0.10.5; shipping a daemon-startup regression to fix a performance nuisance would be a bad trade.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant