Repository navigation
M5: threat-model review, service-hosted WebDAV attach, double-mount diagnosis - #3
Merged
Merged
Conversation
…iagnosis - docs/design/05-threat-model.md section 12: the M5 implementation review. Per surface (pipes, loopback WebDAV, Dokany requestor check, update signatures, DPAPI, Event Log, service inputs) the claimed property, the code that carries it (file:line) and a verdict. Claims with no code: the host compiles in no update roots, no crash-dump code, no Event 3100, no FUS/RDP session list, no --elevated-view. Proposed TM-R40 to TM-R45. - Global WebDAV attach (02 7.7): the service hosts the export and maps nothing (no_map); each user's agent maps the UNC it gets from the new service method mount.attach_info, at logon and through mount.attach. Only members of service_access get the UNC. The mechanism goes through svc_gw_decide (auto; system/link verdicts are refused, not served as attach). backend = webdav service profiles load again. - sd_webdav service mode: the per-connection owner check yields to the per-request sd_vfs check, and the token's enabled groups reach sd_vfs so group entries of service_access match. Anonymous and Guest callers get 403; LOCAL/NETWORK SERVICE match only by an explicit SID. - The agent's own mount-start cleanup keeps the attached service port (sd_webdav_preserve_port); the explicit start/stop cleanups still remove it. - The Part 10 heap corruption on a second mount was a test double free, fixed by json_drop in e2c3df5; recorded and verified. - docs/dev/lab-results.md appendix: the procedure for the live global Dokany mount on the Win10 VM (not run: the VM was in use). Tests: unit.svc (attach point reported; attach refused outside service_access), unit.webdav (service server admits a member and refuses a non-member, real SSPI). Local gate: five presets pass; repro.x64-modern passes.
ChangeServiceConfig2 with SERVICE_CONFIG_FAILURE_ACTIONS needs SERVICE_START on the handle when an action is SC_ACTION_RESTART. scm_set_failure_actions opened the service with SERVICE_CHANGE_CONFIG only, so every install failed with ERROR_ACCESS_DENIED after CreateService, rolled the service back, and the CLI reported it as "needs an elevated console" (exit 9), even as SYSTEM. Found by the live global mount run on the Win10 lab VM: with this change the service installs, starts and mounts the global drive.
Machine profiles of scope service with [mount] autostart = boot were never mounted by the service (03 3.4.1): the live lab run found every reboot with an empty service list until a manual service mount. The body of the mount handler becomes svc_mount_queue, shared by the IPC "mount" request and the new svc_autostart_boot_mounts, which runs after the profiles are loaded at start, on config reload and after a data-folder repair. The SCM start is not blocked: the mounts go through the control thread's queue with sd_mount's reconnect backoff, so a network that is not up yet only delays them. An already mounted profile is left alone, so a reload never double-mounts.
…sing elevation
service mount, unmount and status now pass the requested name to fail_ipc,
so "There is no profile named {name}." shows the profile instead of an
empty placeholder (m5-notes known defect). scm_error splits
ERROR_ACCESS_DENIED by cli_is_elevated: a caller that is not elevated
keeps the elevation message and exit 9 (new cli.service.manage_need_admin,
"Managing the ShellDrive service ...", more accurate for install,
uninstall, start and stop), while an elevated console whose SCM call was
refused gets cli.service.scm_failed with the Win32 code and exit 1, as
03 6.1 assigns. SHELLDRIVE_TEST_SCM=denied drives both branches without
touching the real SCM; a smoke test and an e2e test cover them and the
named profile.
… running' The service pipe's DACL grants read/write to interactive logons only (03 §2.2, carried in the threat model), so a member of service_access in a batch logon is denied at CreateFileW. open_pipe() folded that refusal into SD_E_NOT_FOUND and connect_service() printed 'not running or not installed' with exit 8. open_pipe() now returns SD_E_ACCESS_DENIED on ERROR_ACCESS_DENIED, connect_service() returns the sd_err, and the CLI reports access_denied with exit 9 through a new localized message in all six catalogs. Unit tests pin the service pipe DACL to the design and the new error mapping; a smoke test covers the CLI exit codes and JSON.
run_result is 32 KiB; as a local it trips C6262 under /analyze (x64-analysis), like the other smoke tests it is static.
The live lab run found the boot autostart mount failing (its host key was added later) and then `service mount` of the same profile answering already_mounted (exit 10): the duplicate check matched the final failed record, so the drive could not come up without an unmount first. A final failed record holds no live drive (03 1.11: failed is terminal), so a new mount of its profile now detaches it under g.lock and queues its release to the control thread, the way the tick reaps an unmounted record; the new mount gets the same letter. A live record still answers already_mounted. Tests: a failed autostart mount is replaced by a new mount (new id, same target) and a live one is still refused. wait_state compared the state string after freeing the reply it pointed into (it only bit with a NULL `last` in Debug); it now compares first. The boot autostart tests register their own host-key trust so they also pass when run alone.
The four gaps of the first live run (boot autostart, the batch-session service list report, the unnamed profile, the SCM access-denied message) are fixed and checked live on 2026-10-31, as is the failed-record replacement that run found.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
docs/design/05-threat-model.md§12): what M5 actually implements, surface by surface (pipes, loopback WebDAV, Dokany requestor check, update signatures, DPAPI, Event Log, service inputs). Each claimed property gets afile:lineand a verdict. It finds eight claims with no code behind them. The most serious: the host compiles in no update roots (update_ipc.c:30-31), so the complete verifier is never reachable, and there is no crash-dump code. It proposes TM-R40–TM-R45.mount.attach_info, at logon and throughmount.attach, and only members ofservice_accessget it. The mechanism goes throughsvc_gw_decide:autoresolves to attach, and asystemorlinkverdict is refused rather than served as attach.sd_webdavservice mode. The per-connection owner check yields to the per-requestsd_vfscheck, and token groups now reach it, so group entries inservice_accessmatch. Anonymous and Guest callers get 403. LOCAL SERVICE and NETWORK SERVICE match only by an explicit SID.sd_webdav_preserve_port). Without this, a later per-user WebDAV mount would have cancelled the global drive.json_dropin e2c3df5. This PR records it and verifies it.docs/dev/lab-results.mdappendix). It has not been run: the Win10 VM was busy with M3 acceptance.Test plan
unit.svc: attach point reported for a WebDAV global mount, nothing to attach for Dokany,access_deniedoutsideservice_access, double-mount regressionunit.webdav: a service server admits a member and refuses a non-member (real SSPI, real loopback server)tools\ci-local.ps1 -Pipeline: x64-modern, x64-xp, x86-ia32, x86-sse2 and x64-analysis all pass (only the expected WOW64dokany.mountskips)repro.x64-modernsd-win10-ltsc21-x64, two users, mount at boot (procedure inlab-results.md)Details and open items:
docs/dev/m5-notes.mdPart 14.Live global Dokany mount on the Win10 lab VM (2026-10-30)
58d6b68service: open the service with SERVICE_START to set its restart actions. The live run foundthat
service installcould never succeed.ChangeServiceConfig2(SERVICE_CONFIG_FAILURE_ACTIONS)withSC_ACTION_RESTARTneedsSERVICE_STARTon the handle, so the install rolled back with ACCESS_DENIED, andthe CLI reported it as "needs an elevated console" (exit 9), even as SYSTEM.
f853144docs: record the M5 live global mount run. These passed on the run: install and start asSYSTEM, machine profile and host key, the global mount (Event 1100), standard-user batch and console
logons, and two live sessions at once (G1). G5 passed: 1 GiB written in 74.6 s and read back in 17.6 s
with a matching SHA-256. G7 passed: the read-only member is denied, and the drop-member mutation takes
effect. G3 passed: service stop/start and VM save/resume. Uninstall passed, and Event 1200 records every
change with the user SID.
Gaps to triage are in
m5-notes.mdPart 14 "Left":autostart = bootis not applied by the service.service listexits 8 from a batch session.scm_errormaps every ACCESS_DENIED to "needs an elevated console".Local gate on
f853144: all five presets pass.Gap fixes, checked by a second live run (2026-10-31)
5780848service: mount autostart=boot profiles when the service starts. After a reboot the drive ismounted before logon, 33 s and 35 s after boot on the two phase-B reboots, so G6 holds for Dokany.
7f4538ccli: name the profile in service errors and tell SCM denials from missing elevation.b478b45cli: report a refused service-pipe connect as access denied, not "not running". The pipestays interactive-only, as 03 §2.2 specifies. A batch session now gets exit 9 "Access to the ShellDrive
service is denied.", and exit 8 means the service is really absent.
9b26bfcservice: let a new mount replace a failed record of the same profile. The live run found it:the first autostart after install fails until the host key exists, and that failed record then blocked
service mountwith exit 10. This commit also fixes a use-after-free in thewait_statetest helper.3b9f032tests: keeps a 32 KiB smoke result off the stack (C6262 under/analyze).0ec4afadocs: updatesm5-notes.mdPart 14.Local gate on
9b26bfc: all five presets pass. Live run on the Win10 lab VM: 45 steps OK,SDM5 DONE.Three final-phase steps failed because the lab script did not yet expect the autostart (
already_mountedafter a service restart). The script is fixed; the product behaviour is correct.