<#42 Windows secret ACL, real ownership checks, at...
# osctrl
g
#42 Windows secret ACL, real ownership checks, atomic file writes, safe Authenticode command Pull request opened by javuto Windows secret ACL, real ownership checks, atomic file writes, safe Authenticode command Problem Findings from a survey of osctrld file handling: • Secret readable by all local users on Windows.
install
wrote the enrollment secret with a mode that does nothing on Windows, so the file inherited the Program Files ACL (Users can read). After any upgrade restore, the restore code also applied a protected ACL that explicitly grants Users read and execute, undoing hardening. • Windows ownership checks always passed.
adminOwnedDir
returned true and
adminOwnedFile
re-applied the ACL before trusting the file. With
osquery
pointing at a user-writable folder, a user could pre-create
extensions.load
. • Writes were not atomic and followed symlinks.
writeContentExists
used
os.WriteFile
plus
os.Chmod
, so a symlink at the flagfile, certificate or secret path was followed, and a crash could leave a truncated file. Several writers did not fsync before rename. • PowerShell path interpolation. The Authenticode check built
Get-AuthenticodeSignature '<path>'
by interpolating the path into a command string. Change • Secret ACL. • The secret file gets a protected DACL: SYSTEM and Administrators full control, nothing else, nothing inheritable (
D:P(A;;FA;;;SY)(A;;FA;;;BA)
). osqueryd runs as SYSTEM, so it can still read it. • On Windows the secret's temp file is created with that ACL in the same
CreateFile
call, so there is no window where it inherits the folder's ACL. •
install
re-applies it when the content is unchanged, which fixes hosts installed earlier. If that fails it warns. • Upgrade restore writes the secret through the same path and no longer hands it to the Users-readable owner ACL. • The flagfile, certificate and autoload file keep their existing ACL. A portable rule (
fileACLFor
) decides which class applies to which path. • Ownership checks.
adminOnlyWrite
decides from the owner and the DACL. • The owner must be SYSTEM, Administrators or TrustedInstaller. • No other principal may hold write, append, delete-child, delete, write-DAC, write-owner or generic write/all rights. • Inheritable entries count, and CREATOR OWNER is accepted. •
adminOwnedDir
now checks for a real directory and then the ACL, and refuses autoload writes when unsafe, with the same error as POSIX. •
adminOwnedFile
checks for a regular file and then only reads owner and ACL. It no longer re-applies the ACL first. • The default
C:\Program Files\osquery
ACL and osquery's own safe-permissions ACL pass. • Atomic writes. •
writeContentExists
goes through `writeFileAtomic`: a symlink at the target is replaced, not followed. Directories and devices are refused. The temp file is created with an unpredictable name and its mode is set before writing. It is fsynced, renamed over the target, and the directory is fsynced on POSIX. The POSIX owner of a replaced file is preserved. •
installExtension
,
updateAutoload
and
stashPackage
also fsync before and after the rename. • Authenticode.
authenticodeCommand
passes the path in the environment variable
OSCTRLD_PKG_PATH
and uses
-LiteralPath
, so the path is never part of the script and never treated as a wildcard. Failures now include the PowerShell output. •
ARCHITECTURE.md
and
README.md
updated (file-write semantics, Windows ACL classes, ownership checks, hardening note with an
icacls
check). Security / operational impact • The secret is no longer readable by ordinary Windows users. Non-elevated tools running as a regular user can no longer read it. • Behaviour changes: • On Windows, a
osquery
folder whose ACL lets non-admins write, or that a user account owns, now refuses autoload writes. Extensions then report errors. The default MSI location passes. For a hand-created admin folder owned by a user SID, fix with
icacls <dir> /setowner Administrators
. • Existing Windows extension files whose ACL is not admin-only are downloaded again instead of silently adopted. • A symlink at the flagfile, certificate or secret path now needs
--force
and is replaced with a regular file. The daemon always forces, so it replaces it once and restarts osquery. • Directories and devices at those paths are refused. • On Windows, flags and certificate get the folder's inherited ACL on each rewrite. • Non-root runs need write access to the directory, not only the file. The shipped units run as root. • Not changed: uninstall (deletes the file, and unlinks a symlink instead of following it). Validation •
go test ./... -count=1 -race
,
go vet
for linux, darwin and windows,
GOOS=windows go build
,
gofmt
,
golangci-lint
(also for Windows and Linux). • Tests seen failing first: • a symlinked target was followed and chmodded by the old code; • no partial file on a failed write; • the secret temp file recorded as restricted; • restore no longer hands the secret to the Users-readable owner ACL; • the ACL rule and the secret SDDL; • every unsafe case in the ownership table; • the PowerShell path staying out of the script. • No failing run: the umask test (the old code chmodded after writing as well), the fsync additions and POSIX owner preservation, which cannot be observed or need root. The new Windows-only tests compile and vet but have not run. • Needs a Windows host: the 11-step checklist with
icacls
and PowerShell commands. It covers a fresh install, re-install on an existing host, osqueryd still enrolling, upgrade and rollback, a refused unsafe directory, flag rewrites, the Authenticode check with a quote in the path, and extension adoption. Known limits • Deny entries are ignored and object or callback ACEs count as untrusted, so an exotic but safe ACL can be judged unsafe. • Rename over an existing file can fail if another process holds it open without delete sharing (for example antivirus). Needs a check on a real host. • The secret is matched by path, so an 8.3 short name or a hard link is not recognised as the secret. • The quick-add script's
Set-SafePermissions
is applied to the
osqueryd
subfolder, not
C:\Program Files\osquery
. The secret is readable because that folder inherits the Program Files ACL. jmpsec/osctrld