@openclaw/fs-safe 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +88 -0
- package/README.md +38 -10
- package/dist/advanced.d.ts +2 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +2 -1
- package/dist/archive-kind.d.ts +0 -1
- package/dist/archive-kind.d.ts.map +1 -1
- package/dist/archive-kind.js +5 -17
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +113 -46
- package/dist/archive-parser.wasm +0 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +10 -11
- package/dist/archive-tar-stream.d.ts +3 -0
- package/dist/archive-tar-stream.d.ts.map +1 -1
- package/dist/archive-tar-stream.js +56 -37
- package/dist/archive-tar-wasm.d.ts +16 -4
- package/dist/archive-tar-wasm.d.ts.map +1 -1
- package/dist/archive-tar-wasm.js +134 -34
- package/dist/archive-zip-directory.d.ts +4 -0
- package/dist/archive-zip-directory.d.ts.map +1 -1
- package/dist/archive-zip-directory.js +2 -0
- package/dist/archive-zip-entry.d.ts +6 -2
- package/dist/archive-zip-entry.d.ts.map +1 -1
- package/dist/archive-zip-entry.js +23 -8
- package/dist/archive-zip-integrity.d.ts.map +1 -1
- package/dist/archive-zip-integrity.js +3 -4
- package/dist/archive-zip-loader.d.ts.map +1 -1
- package/dist/archive-zip-loader.js +107 -31
- package/dist/archive-zip-names.d.ts +1 -0
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +6 -0
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +11 -11
- package/dist/bounded-read-stream.d.ts +0 -1
- package/dist/bounded-read-stream.d.ts.map +1 -1
- package/dist/bounded-read-stream.js +0 -6
- package/dist/clone-metadata.d.ts +1 -0
- package/dist/clone-metadata.d.ts.map +1 -1
- package/dist/clone-metadata.js +6 -2
- package/dist/copy-publication.d.ts +6 -0
- package/dist/copy-publication.d.ts.map +1 -1
- package/dist/copy-publication.js +3 -0
- package/dist/copy-tree-portable.d.ts.map +1 -1
- package/dist/copy-tree-portable.js +44 -24
- package/dist/copy.d.ts.map +1 -1
- package/dist/copy.js +29 -11
- package/dist/create-directory.d.ts +20 -0
- package/dist/create-directory.d.ts.map +1 -0
- package/dist/create-directory.js +130 -0
- package/dist/create-file-async.d.ts +7 -0
- package/dist/create-file-async.d.ts.map +1 -0
- package/dist/create-file-async.js +121 -0
- package/dist/create-file.d.ts +8 -0
- package/dist/create-file.d.ts.map +1 -0
- package/dist/create-file.js +190 -0
- package/dist/create-owned-file.d.ts +8 -0
- package/dist/create-owned-file.d.ts.map +1 -0
- package/dist/create-owned-file.js +16 -0
- package/dist/create.d.ts +4 -0
- package/dist/create.d.ts.map +1 -0
- package/dist/create.js +2 -0
- package/dist/creation-darwin.d.ts +7 -0
- package/dist/creation-darwin.d.ts.map +1 -0
- package/dist/creation-darwin.js +79 -0
- package/dist/creation-file-state.d.ts +19 -0
- package/dist/creation-file-state.d.ts.map +1 -0
- package/dist/creation-file-state.js +118 -0
- package/dist/creation-path.d.ts +21 -0
- package/dist/creation-path.d.ts.map +1 -0
- package/dist/creation-path.js +71 -0
- package/dist/creation-permissions.d.ts +19 -0
- package/dist/creation-permissions.d.ts.map +1 -0
- package/dist/creation-permissions.js +125 -0
- package/dist/directory-durability.d.ts +1 -1
- package/dist/directory-durability.d.ts.map +1 -1
- package/dist/directory-durability.js +22 -80
- package/dist/directory-guard.d.ts +3 -0
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-mode-node.d.ts +2 -0
- package/dist/directory-mode-node.d.ts.map +1 -1
- package/dist/directory-mode-node.js +8 -0
- package/dist/directory-mode-owner.js +5 -5
- package/dist/directory-receipt.d.ts +24 -0
- package/dist/directory-receipt.d.ts.map +1 -0
- package/dist/directory-receipt.js +127 -0
- package/dist/file-cleanup.d.ts +19 -0
- package/dist/file-cleanup.d.ts.map +1 -0
- package/dist/file-cleanup.js +78 -0
- package/dist/file-handle-transfer.d.ts +2 -0
- package/dist/file-handle-transfer.d.ts.map +1 -1
- package/dist/file-handle-transfer.js +57 -2
- package/dist/file-identity.d.ts.map +1 -1
- package/dist/file-identity.js +18 -4
- package/dist/file-lock-sync-admission.d.ts +19 -0
- package/dist/file-lock-sync-admission.d.ts.map +1 -0
- package/dist/file-lock-sync-admission.js +93 -0
- package/dist/file-lock-sync-root-acquire.d.ts +4 -0
- package/dist/file-lock-sync-root-acquire.d.ts.map +1 -0
- package/dist/file-lock-sync-root-acquire.js +370 -0
- package/dist/file-lock-sync-root-arbitration.d.ts +18 -0
- package/dist/file-lock-sync-root-arbitration.d.ts.map +1 -0
- package/dist/file-lock-sync-root-arbitration.js +66 -0
- package/dist/file-lock-sync-root-held.d.ts +34 -0
- package/dist/file-lock-sync-root-held.d.ts.map +1 -0
- package/dist/file-lock-sync-root-held.js +393 -0
- package/dist/file-lock-sync-root-io.d.ts +44 -0
- package/dist/file-lock-sync-root-io.d.ts.map +1 -0
- package/dist/file-lock-sync-root-io.js +209 -0
- package/dist/file-lock-sync-root-mutation.d.ts +17 -0
- package/dist/file-lock-sync-root-mutation.d.ts.map +1 -0
- package/dist/file-lock-sync-root-mutation.js +277 -0
- package/dist/file-lock-sync-root-options.d.ts +20 -0
- package/dist/file-lock-sync-root-options.d.ts.map +1 -0
- package/dist/file-lock-sync-root-options.js +58 -0
- package/dist/file-lock-sync-root-registration.d.ts +2 -0
- package/dist/file-lock-sync-root-registration.d.ts.map +1 -0
- package/dist/file-lock-sync-root-registration.js +90 -0
- package/dist/file-lock-sync-root.d.ts +36 -0
- package/dist/file-lock-sync-root.d.ts.map +1 -0
- package/dist/file-lock-sync-root.js +361 -0
- package/dist/file-lock-sync-stale-admission.d.ts +24 -0
- package/dist/file-lock-sync-stale-admission.d.ts.map +1 -0
- package/dist/file-lock-sync-stale-admission.js +205 -0
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +245 -205
- package/dist/file-observation.d.ts +1 -1
- package/dist/file-observation.d.ts.map +1 -1
- package/dist/file-store-boundary.d.ts +2 -6
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +3 -9
- package/dist/file-store-prune.d.ts.map +1 -1
- package/dist/file-store-prune.js +5 -1
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +5 -8
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +47 -12
- package/dist/guarded-mkdir.d.ts +1 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +36 -7
- package/dist/json-document-store.d.ts.map +1 -1
- package/dist/json-document-store.js +22 -15
- package/dist/json-durable-queue-ownership.d.ts +0 -1
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +0 -6
- package/dist/move-path.js +1 -1
- package/dist/native-binding.d.ts +13 -1
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-fallback-warning.d.ts +4 -0
- package/dist/native-fallback-warning.d.ts.map +1 -0
- package/dist/native-fallback-warning.js +11 -0
- package/dist/native-operations.d.ts +0 -2
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +0 -24
- package/dist/native-parent-admission.d.ts +5 -2
- package/dist/native-parent-admission.d.ts.map +1 -1
- package/dist/native-parent-admission.js +27 -7
- package/dist/native-pinned-write-windows.d.ts +1 -1
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +174 -29
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +26 -14
- package/dist/native-policy-parent-windows.d.ts +14 -0
- package/dist/native-policy-parent-windows.d.ts.map +1 -0
- package/dist/native-policy-parent-windows.js +209 -0
- package/dist/native-rename-outcome.d.ts +4 -0
- package/dist/native-rename-outcome.d.ts.map +1 -0
- package/dist/native-rename-outcome.js +8 -0
- package/dist/native-staged-file.d.ts +3 -2
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +121 -72
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +12 -8
- package/dist/owner-dacl.d.ts.map +1 -1
- package/dist/owner-dacl.js +10 -4
- package/dist/path-prefix.d.ts.map +1 -1
- package/dist/path-prefix.js +30 -8
- package/dist/path-suffix-aliases.d.ts +2 -0
- package/dist/path-suffix-aliases.d.ts.map +1 -1
- package/dist/path-suffix-aliases.js +25 -17
- package/dist/permission-exec.d.ts +2 -0
- package/dist/permission-exec.d.ts.map +1 -1
- package/dist/permission-exec.js +150 -21
- package/dist/permissions-windows.js +1 -1
- package/dist/pinned-mutation-admission.d.ts.map +1 -1
- package/dist/pinned-mutation-admission.js +10 -5
- package/dist/pinned-mutation-observation.d.ts +0 -1
- package/dist/pinned-mutation-observation.d.ts.map +1 -1
- package/dist/pinned-mutation-observation.js +0 -19
- package/dist/pinned-mutation-shared-route.d.ts +1 -0
- package/dist/pinned-mutation-shared-route.d.ts.map +1 -1
- package/dist/pinned-mutation-shared-route.js +1 -1
- package/dist/pinned-write-input.d.ts +4 -0
- package/dist/pinned-write-input.d.ts.map +1 -0
- package/dist/pinned-write-input.js +25 -0
- package/dist/pinned-write-mode.d.ts +5 -0
- package/dist/pinned-write-mode.d.ts.map +1 -0
- package/dist/pinned-write-mode.js +24 -0
- package/dist/pinned-write-staged.d.ts +6 -0
- package/dist/pinned-write-staged.d.ts.map +1 -0
- package/dist/pinned-write-staged.js +187 -0
- package/dist/pinned-write-types.d.ts +5 -0
- package/dist/pinned-write-types.d.ts.map +1 -1
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +35 -145
- package/dist/private-directory.d.ts.map +1 -1
- package/dist/private-directory.js +18 -4
- package/dist/private-producer-handoff-sync.d.ts +14 -0
- package/dist/private-producer-handoff-sync.d.ts.map +1 -0
- package/dist/private-producer-handoff-sync.js +114 -0
- package/dist/private-producer-handoff.d.ts +22 -4
- package/dist/private-producer-handoff.d.ts.map +1 -1
- package/dist/private-producer-handoff.js +140 -77
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +75 -121
- package/dist/publish-copy-stage.d.ts +2 -1
- package/dist/publish-copy-stage.d.ts.map +1 -1
- package/dist/publish-copy-stage.js +16 -7
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +2 -2
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +1 -15
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +256 -18
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +62 -70
- package/dist/replace-file-copy-source.d.ts.map +1 -1
- package/dist/replace-file-copy-source.js +10 -12
- package/dist/replace-file-temp-owner.d.ts +5 -9
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +56 -72
- package/dist/replace-file.js +6 -6
- package/dist/retained-directory-replacement.d.ts +26 -0
- package/dist/retained-directory-replacement.d.ts.map +1 -0
- package/dist/retained-directory-replacement.js +193 -0
- package/dist/root-boundary.d.ts +1 -0
- package/dist/root-boundary.d.ts.map +1 -1
- package/dist/root-boundary.js +4 -0
- package/dist/root-context.d.ts +0 -8
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +0 -3
- package/dist/root-create-input.d.ts +8 -1
- package/dist/root-create-input.d.ts.map +1 -1
- package/dist/root-create-input.js +17 -4
- package/dist/root-directory-creation.d.ts +3 -3
- package/dist/root-directory-creation.d.ts.map +1 -1
- package/dist/root-directory-creation.js +15 -3
- package/dist/root-directory-list.d.ts +1 -0
- package/dist/root-directory-list.d.ts.map +1 -1
- package/dist/root-directory-list.js +1 -0
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +46 -17
- package/dist/root-move-noreplace.d.ts.map +1 -1
- package/dist/root-move-noreplace.js +24 -15
- package/dist/root-options.d.ts +12 -4
- package/dist/root-options.d.ts.map +1 -1
- package/dist/root-path-errors.d.ts +1 -0
- package/dist/root-path-errors.d.ts.map +1 -1
- package/dist/root-path-errors.js +11 -2
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +11 -35
- package/dist/root-path-stat.d.ts.map +1 -1
- package/dist/root-path-stat.js +59 -7
- package/dist/root-path.js +1 -13
- package/dist/root-remove.d.ts +1 -0
- package/dist/root-remove.d.ts.map +1 -1
- package/dist/root-remove.js +4 -0
- package/dist/root-walk.d.ts +1 -1
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +17 -2
- package/dist/root-write-admission.d.ts +0 -2
- package/dist/root-write-admission.d.ts.map +1 -1
- package/dist/root-write-admission.js +1 -15
- package/dist/root-write-complete-parent.d.ts.map +1 -1
- package/dist/root-write-complete-parent.js +7 -23
- package/dist/root-write-publication.js +1 -1
- package/dist/root-write-verification.d.ts.map +1 -1
- package/dist/root-write-verification.js +29 -42
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +3 -24
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +3 -24
- package/dist/secret-read-policy.d.ts +6 -2
- package/dist/secret-read-policy.d.ts.map +1 -1
- package/dist/secret-read-policy.js +26 -2
- package/dist/secure-file-windows.d.ts +6 -0
- package/dist/secure-file-windows.d.ts.map +1 -1
- package/dist/secure-file-windows.js +34 -117
- package/dist/secure-file.js +2 -2
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +23 -12
- package/dist/sidecar-lock-acquire.d.ts +2 -28
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +288 -199
- package/dist/sidecar-lock-admission-context.d.ts +19 -0
- package/dist/sidecar-lock-admission-context.d.ts.map +1 -0
- package/dist/sidecar-lock-admission-context.js +60 -0
- package/dist/sidecar-lock-admission-parser.d.ts +43 -0
- package/dist/sidecar-lock-admission-parser.d.ts.map +1 -0
- package/dist/sidecar-lock-admission-parser.js +113 -0
- package/dist/sidecar-lock-admission.d.ts +35 -0
- package/dist/sidecar-lock-admission.d.ts.map +1 -0
- package/dist/sidecar-lock-admission.js +7 -0
- package/dist/sidecar-lock-reclaim.d.ts +9 -4
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +80 -25
- package/dist/sidecar-lock-root.d.ts.map +1 -1
- package/dist/sidecar-lock-root.js +2 -1
- package/dist/sidecar-lock-stale-admission.d.ts +39 -0
- package/dist/sidecar-lock-stale-admission.d.ts.map +1 -0
- package/dist/sidecar-lock-stale-admission.js +232 -0
- package/dist/sidecar-lock-target.d.ts +8 -0
- package/dist/sidecar-lock-target.d.ts.map +1 -0
- package/dist/sidecar-lock-target.js +55 -0
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +100 -16
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/staged-directory.js +6 -6
- package/dist/staged-file-settlement.d.ts +17 -0
- package/dist/staged-file-settlement.d.ts.map +1 -0
- package/dist/staged-file-settlement.js +57 -0
- package/dist/temp-workspace-descriptor.d.ts.map +1 -1
- package/dist/temp-workspace-descriptor.js +9 -27
- package/dist/temp-workspace-owner.d.ts.map +1 -1
- package/dist/temp-workspace-owner.js +8 -8
- package/dist/walk.d.ts +5 -1
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +19 -6
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +4 -3
- package/dist/windows-security-bridge.cs +336 -0
- package/dist/windows-security-bridge.ps1 +15 -0
- package/dist/windows-security-command.d.ts +26 -0
- package/dist/windows-security-command.d.ts.map +1 -0
- package/dist/windows-security-command.js +363 -0
- package/dist/windows-security-facts.d.ts +6 -0
- package/dist/windows-security-facts.d.ts.map +1 -0
- package/dist/windows-security-facts.js +108 -0
- package/docs/advanced.md +4 -2
- package/docs/archive.md +97 -46
- package/docs/atomic.md +85 -8
- package/docs/config.md +6 -2
- package/docs/contributing.md +44 -4
- package/docs/copy.md +37 -0
- package/docs/creation.md +128 -0
- package/docs/durability.md +24 -0
- package/docs/file-store.md +19 -0
- package/docs/install.md +31 -7
- package/docs/json-store.md +5 -0
- package/docs/migrating-to-0.5.md +15 -6
- package/docs/migrating-to-0.6.md +9 -4
- package/docs/native-helper.md +32 -12
- package/docs/native.md +38 -7
- package/docs/output.md +6 -0
- package/docs/path-prefix.md +10 -0
- package/docs/path-suffix-aliases.md +51 -6
- package/docs/permissions.md +50 -14
- package/docs/public-api.md +3 -2
- package/docs/root.md +56 -3
- package/docs/secret-file.md +11 -2
- package/docs/secure-file.md +9 -4
- package/docs/sidecar-lock.md +114 -8
- package/docs/staged-file.md +12 -3
- package/docs/temp.md +20 -3
- package/docs/walk.md +67 -1
- package/docs/writing.md +76 -6
- package/package.json +19 -16
- package/dist/darwin-acl.d.ts +0 -4
- package/dist/darwin-acl.d.ts.map +0 -1
- package/dist/darwin-acl.js +0 -24
|
@@ -185,18 +185,15 @@ export class TempWorkspaceRetainedChild {
|
|
|
185
185
|
return descriptor;
|
|
186
186
|
}
|
|
187
187
|
async #assertProcAuthority(fd, ownerUid) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
const procPath = `/proc/self/fd/${fd}`;
|
|
192
|
-
const opened = inspectFileIdentitySync(() => fsSync.fstatSync(fd, { bigint: true }), this.#identity);
|
|
193
|
-
const followed = inspectFileIdentitySync(() => fsSync.statSync(procPath, { bigint: true }), this.#identity);
|
|
194
|
-
assertOwnedDirectory(opened, followed);
|
|
195
|
-
assertTempWorkspaceChildState(opened, ownerUid);
|
|
196
|
-
assertTempWorkspaceChildState(followed, ownerUid);
|
|
188
|
+
const filesystem = await fs.statfs("/proc/self/fd", { bigint: true });
|
|
189
|
+
this.#assertProcDescriptorAuthority(fd, ownerUid, filesystem.type);
|
|
197
190
|
}
|
|
198
191
|
#assertProcAuthoritySync(fd, ownerUid) {
|
|
199
|
-
|
|
192
|
+
const filesystem = fsSync.statfsSync("/proc/self/fd", { bigint: true });
|
|
193
|
+
this.#assertProcDescriptorAuthority(fd, ownerUid, filesystem.type);
|
|
194
|
+
}
|
|
195
|
+
#assertProcDescriptorAuthority(fd, ownerUid, filesystemType) {
|
|
196
|
+
if (filesystemType !== 0x9fa0n) {
|
|
200
197
|
throw new FsSafeError("path-mismatch", "directory mode requires a trusted procfs fd namespace");
|
|
201
198
|
}
|
|
202
199
|
const procPath = `/proc/self/fd/${fd}`;
|
|
@@ -309,13 +306,7 @@ export class TempWorkspaceRetainedChild {
|
|
|
309
306
|
catch (error) {
|
|
310
307
|
// The readable replacement has not been installed. Give it exactly one
|
|
311
308
|
// close attempt and leave neither indeterminate descriptor owned here.
|
|
312
|
-
|
|
313
|
-
fsSync.closeSync(fd);
|
|
314
|
-
}
|
|
315
|
-
catch (closeError) {
|
|
316
|
-
throw new AggregateError([error, closeError], "temp workspace child descriptor replacement close failed");
|
|
317
|
-
}
|
|
318
|
-
throw error;
|
|
309
|
+
closeAfterAdmissionFailure(fd, error, "temp workspace child descriptor replacement close failed");
|
|
319
310
|
}
|
|
320
311
|
this.#fd = fd;
|
|
321
312
|
this.#access = "read";
|
|
@@ -334,16 +325,7 @@ export class TempWorkspaceRetainedChild {
|
|
|
334
325
|
const fd = this.#fd;
|
|
335
326
|
this.#fd = undefined;
|
|
336
327
|
if (retainDescriptor && !this.canEnumerate) {
|
|
337
|
-
|
|
338
|
-
fsSync.closeSync(fd);
|
|
339
|
-
}
|
|
340
|
-
catch (closeError) {
|
|
341
|
-
throw new AggregateError([
|
|
342
|
-
new FsSafeError("helper-unavailable", "temp workspace cleanup requires a readable child descriptor"),
|
|
343
|
-
closeError,
|
|
344
|
-
], "temp workspace child descriptor rejection and close failed");
|
|
345
|
-
}
|
|
346
|
-
throw new FsSafeError("helper-unavailable", "temp workspace cleanup requires a readable child descriptor");
|
|
328
|
+
closeAfterAdmissionFailure(fd, new FsSafeError("helper-unavailable", "temp workspace cleanup requires a readable child descriptor"), "temp workspace child descriptor rejection and close failed");
|
|
347
329
|
}
|
|
348
330
|
if (!retainDescriptor) {
|
|
349
331
|
fsSync.closeSync(fd);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"temp-workspace-owner.d.ts","sourceRoot":"","sources":["../src/temp-workspace-owner.ts"],"names":[],"mappings":"AAOA,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAEnE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAChF,OAAO,EAEL,0BAA0B,EAE1B,KAAK,iBAAiB,EACvB,MAAM,gCAAgC,CAAC;AAGxC,MAAM,MAAM,0BAA0B,GAAG,SAAS,GAAG,SAAS,GAAG,mBAAmB,GAAG,eAAe,CAAC;AACvG,MAAM,MAAM,0BAA0B,GAAG,YAAY,GAAG,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"temp-workspace-owner.d.ts","sourceRoot":"","sources":["../src/temp-workspace-owner.ts"],"names":[],"mappings":"AAOA,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAEnE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAChF,OAAO,EAEL,0BAA0B,EAE1B,KAAK,iBAAiB,EACvB,MAAM,gCAAgC,CAAC;AAGxC,MAAM,MAAM,0BAA0B,GAAG,SAAS,GAAG,SAAS,GAAG,mBAAmB,GAAG,eAAe,CAAC;AACvG,MAAM,MAAM,0BAA0B,GAAG,YAAY,GAAG,iBAAiB,CAAC;AAuB1E,qBAAa,8BAA8B;;IACzC,QAAQ,CAAC,OAAO,EAAE,aAAa,GAAG,SAAS,CAAC;IAC5C,QAAQ,CAAC,MAAM,EAAE,iBAAiB,GAAG,SAAS,CAAC;IAM/C,YACE,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,0BAA0B,EAClC,SAAS,EAAE,0BAA0B,EACrC,OAAO,EAAE,MAAM,EAuEhB;IAED,IAAI,kBAAkB,IAAI,OAAO,CAGhC;IAED,oBAAoB,IAAI,IAAI,CAiB3B;IAUD,oBAAoB,CAAC,YAAY,EAAE,OAAO,GAAG,OAAO,CAenD;IAED,aAAa,IAAI,IAAI,CAEpB;IAED,qBAAqB,IAAI,IAAI,CAE5B;IAED,KAAK,IAAI,IAAI,CAIZ;CACF;AAED,qBAAa,yBAAyB;;IAWpC,YACE,QAAQ,EAAE,0BAA0B,EACpC,UAAU,EAAE,8BAA8B,EAC1C,gBAAgB,EAAE,OAAO,EAO1B;IAkLD,OAAO,IAAI,OAAO,CAAC,0BAA0B,CAAC,CAM7C;IAED,WAAW,IAAI,0BAA0B,CAexC;CACF"}
|
|
@@ -294,22 +294,22 @@ export class TempWorkspaceCleanupOwner {
|
|
|
294
294
|
await beforeNativeRemoval(quarantine.path);
|
|
295
295
|
return this.#mapRemoval(await this.#capability.binding.removeOwnedTree(this.#capability.parent.fd, quarantine.name, this.#directory.fd));
|
|
296
296
|
}
|
|
297
|
-
let
|
|
297
|
+
let removalFailure;
|
|
298
298
|
try {
|
|
299
299
|
this.#assertQuarantine(quarantine);
|
|
300
300
|
try {
|
|
301
301
|
await fs.rm(quarantine.path, { recursive: true, force: true });
|
|
302
302
|
}
|
|
303
303
|
catch (error) {
|
|
304
|
-
|
|
304
|
+
removalFailure = { error };
|
|
305
305
|
throw error;
|
|
306
306
|
}
|
|
307
307
|
this.#capability.assertCurrent();
|
|
308
308
|
return "removed";
|
|
309
309
|
}
|
|
310
310
|
catch (error) {
|
|
311
|
-
if (
|
|
312
|
-
throw error;
|
|
311
|
+
if (removalFailure !== undefined)
|
|
312
|
+
throw removalFailure.error;
|
|
313
313
|
return "indeterminate";
|
|
314
314
|
}
|
|
315
315
|
}
|
|
@@ -318,22 +318,22 @@ export class TempWorkspaceCleanupOwner {
|
|
|
318
318
|
getFsSafeTestHooks()?.beforeTempWorkspaceNativeRemovalSync?.(quarantine.path);
|
|
319
319
|
return this.#mapRemoval(this.#capability.binding.removeOwnedTreeSync(this.#capability.parent.fd, quarantine.name, this.#directory.fd));
|
|
320
320
|
}
|
|
321
|
-
let
|
|
321
|
+
let removalFailure;
|
|
322
322
|
try {
|
|
323
323
|
this.#assertQuarantine(quarantine);
|
|
324
324
|
try {
|
|
325
325
|
fsSync.rmSync(quarantine.path, { recursive: true, force: true });
|
|
326
326
|
}
|
|
327
327
|
catch (error) {
|
|
328
|
-
|
|
328
|
+
removalFailure = { error };
|
|
329
329
|
throw error;
|
|
330
330
|
}
|
|
331
331
|
this.#capability.assertCurrent();
|
|
332
332
|
return "removed";
|
|
333
333
|
}
|
|
334
334
|
catch (error) {
|
|
335
|
-
if (
|
|
336
|
-
throw error;
|
|
335
|
+
if (removalFailure !== undefined)
|
|
336
|
+
throw removalFailure.error;
|
|
337
337
|
return "indeterminate";
|
|
338
338
|
}
|
|
339
339
|
}
|
package/dist/walk.d.ts
CHANGED
|
@@ -16,6 +16,10 @@ export type WalkDirectoryOptions = {
|
|
|
16
16
|
include?: (entry: WalkDirectoryEntry) => boolean;
|
|
17
17
|
descend?: (entry: WalkDirectoryEntry) => boolean;
|
|
18
18
|
};
|
|
19
|
+
export type AsyncWalkDirectoryOptions = Omit<WalkDirectoryOptions, "include" | "descend"> & {
|
|
20
|
+
include?: (entry: WalkDirectoryEntry) => boolean | Promise<boolean>;
|
|
21
|
+
descend?: (entry: WalkDirectoryEntry) => boolean | Promise<boolean>;
|
|
22
|
+
};
|
|
19
23
|
export type WalkDirectoryFailure = {
|
|
20
24
|
path: string;
|
|
21
25
|
relativePath: string;
|
|
@@ -32,6 +36,6 @@ type WalkDirectoryResultWithFailures = WalkDirectoryResult & {
|
|
|
32
36
|
failedDirs: WalkDirectoryFailure[];
|
|
33
37
|
};
|
|
34
38
|
export declare function walkDirectorySync(rootDir: string, options?: WalkDirectoryOptions): WalkDirectoryResultWithFailures;
|
|
35
|
-
export declare function walkDirectory(rootDir: string, options?:
|
|
39
|
+
export declare function walkDirectory(rootDir: string, options?: AsyncWalkDirectoryOptions): Promise<WalkDirectoryResultWithFailures>;
|
|
36
40
|
export {};
|
|
37
41
|
//# sourceMappingURL=walk.d.ts.map
|
package/dist/walk.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"walk.d.ts","sourceRoot":"","sources":["../src/walk.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,SAAS,CAAC;AAS7B,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;AACvE,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE9D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC;IACjD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;IAGnB,UAAU,CAAC,EAAE,oBAAoB,EAAE,CAAC;CACrC,CAAC;AAEF,KAAK,+BAA+B,GAAG,mBAAmB,GAAG;IAC3D,UAAU,EAAE,oBAAoB,EAAE,CAAC;CACpC,CAAC;
|
|
1
|
+
{"version":3,"file":"walk.d.ts","sourceRoot":"","sources":["../src/walk.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,SAAS,CAAC;AAS7B,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;AACvE,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE9D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC;IACjD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAAC,oBAAoB,EAAE,SAAS,GAAG,SAAS,CAAC,GAAG;IAC1F,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACpE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACrE,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;IAGnB,UAAU,CAAC,EAAE,oBAAoB,EAAE,CAAC;CACrC,CAAC;AAEF,KAAK,+BAA+B,GAAG,mBAAmB,GAAG;IAC3D,UAAU,EAAE,oBAAoB,EAAE,CAAC;CACpC,CAAC;AAoFF,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,+BAA+B,CA4DjC;AAED,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,+BAA+B,CAAC,CAiE1C"}
|
package/dist/walk.js
CHANGED
|
@@ -16,6 +16,10 @@ function validateWalkOptions(options) {
|
|
|
16
16
|
throw new TypeError(`invalid walk symlink policy: ${String(options.symlinks)}`);
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
+
function isObjectResult(result) {
|
|
20
|
+
return result !== null &&
|
|
21
|
+
(typeof result === "object" || typeof result === "function");
|
|
22
|
+
}
|
|
19
23
|
function kindForDirent(dirent) {
|
|
20
24
|
if (dirent.isDirectory())
|
|
21
25
|
return "directory";
|
|
@@ -85,6 +89,8 @@ export function walkDirectorySync(rootDir, options = {}) {
|
|
|
85
89
|
let realDir;
|
|
86
90
|
const operationPath = pathForWindowsFilesystem(dir);
|
|
87
91
|
try {
|
|
92
|
+
if (depth > 1 && symlinks !== "follow" && fsSync.lstatSync(operationPath).isSymbolicLink())
|
|
93
|
+
return;
|
|
88
94
|
realDir = realpathSync(operationPath);
|
|
89
95
|
}
|
|
90
96
|
catch (error) {
|
|
@@ -146,6 +152,8 @@ export async function walkDirectory(rootDir, options = {}) {
|
|
|
146
152
|
let realDir;
|
|
147
153
|
const operationPath = pathForWindowsFilesystem(dir);
|
|
148
154
|
try {
|
|
155
|
+
if (depth > 1 && symlinks !== "follow" && fsSync.lstatSync(operationPath).isSymbolicLink())
|
|
156
|
+
return;
|
|
149
157
|
realDir = realpathSync.native(operationPath);
|
|
150
158
|
}
|
|
151
159
|
catch (error) {
|
|
@@ -175,15 +183,20 @@ export async function walkDirectory(rootDir, options = {}) {
|
|
|
175
183
|
continue;
|
|
176
184
|
const relativePath = relativeDir ? `${relativeDir}${path.sep}${dirent.name}` : dirent.name;
|
|
177
185
|
const entry = buildEntry({ relativePath, fullPath, dirent, depth, kind });
|
|
178
|
-
|
|
186
|
+
const include = options.include;
|
|
187
|
+
const included = include == null ? true : Reflect.apply(include, options, [entry]);
|
|
188
|
+
if ((isObjectResult(included) ? await included : included) ?? true) {
|
|
179
189
|
result.entries.push(entry);
|
|
180
190
|
}
|
|
181
191
|
if (kind === "directory" &&
|
|
182
|
-
(options.maxDepth === undefined || depth < options.maxDepth)
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (
|
|
186
|
-
|
|
192
|
+
(options.maxDepth === undefined || depth < options.maxDepth)) {
|
|
193
|
+
const descend = options.descend;
|
|
194
|
+
const descended = descend == null ? true : Reflect.apply(descend, options, [entry]);
|
|
195
|
+
if ((isObjectResult(descended) ? await descended : descended) ?? true) {
|
|
196
|
+
await visit(fullPath, relativePath, depth + 1);
|
|
197
|
+
if (result.truncated)
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
187
200
|
}
|
|
188
201
|
}
|
|
189
202
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"windows-owner.d.ts","sourceRoot":"","sources":["../src/windows-owner.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"windows-owner.d.ts","sourceRoot":"","sources":["../src/windows-owner.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,wBAAwB,EAC9B,MAAM,sBAAsB,CAAC;AAI9B,MAAM,MAAM,gBAAgB,GAAG,CAC7B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,KACX,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEjD,MAAM,MAAM,mBAAmB,GAAG;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,IAAI,CAAC,EAAE,eAAe,EAAE,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,wBAAwB,CAAC;IACvC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAsDF,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,IAAI,EAAE,gBAAgB,CAAC;CACxB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAmD/B"}
|
package/dist/windows-owner.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { formatPermissionErrorDetail, getPermissionCommandFailure, } from "./permission-exec.js";
|
|
1
|
+
import { formatCaughtPermissionFailure, formatPermissionErrorDetail, getPermissionCommandFailure, } from "./permission-exec.js";
|
|
2
2
|
import { resolveWindowsSystemCommand } from "./windows-command.js";
|
|
3
3
|
import { hasWindowsPathAlias } from "./windows-path-alias.js";
|
|
4
4
|
const SID_RE = /^\*?s-\d+-\d+(-\d+)+$/i;
|
|
@@ -14,6 +14,7 @@ function windowsOwnerQueryCommand(targetPath) {
|
|
|
14
14
|
const encodedPath = Buffer.from(targetPath, "utf8").toString("base64");
|
|
15
15
|
return [
|
|
16
16
|
"$ErrorActionPreference='Stop'",
|
|
17
|
+
"$env:PSModulePath=[IO.Path]::Combine($PSHOME,'Modules')",
|
|
17
18
|
`$p=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${encodedPath}'))`,
|
|
18
19
|
"$sections=[System.Security.AccessControl.AccessControlSections]::Access -bor [System.Security.AccessControl.AccessControlSections]::Owner",
|
|
19
20
|
"$acl=if([IO.Directory]::Exists($p)){[IO.Directory]::GetAccessControl($p,$sections)}else{[IO.File]::GetAccessControl($p,$sections)}",
|
|
@@ -29,7 +30,7 @@ function windowsOwnerQueryCommand(targetPath) {
|
|
|
29
30
|
"$raw=[System.Security.AccessControl.RawSecurityDescriptor]::new($acl.GetSecurityDescriptorBinaryForm(),0)",
|
|
30
31
|
"$dacl=$raw.DiscretionaryAcl;$complete=$true",
|
|
31
32
|
"$aces=@(foreach($ace in $dacl){if($ace -isnot [System.Security.AccessControl.CommonAce] -or $ace.IsCallback -or [int]$ace.AceType -notin @(0,1)){$complete=$false;continue};@{sid=$ace.SecurityIdentifier.Value;mask=([long]$ace.AccessMask -band 4294967295);deny=([int]$ace.AceType -eq 1);inheritOnly=([int]$ace.AceFlags -band 8) -ne 0}})",
|
|
32
|
-
"@{ownerSid=$ownerSid;currentUserSid=$currentSid;daclPresent=($null -ne $dacl);aces=$aces;complete=$complete;remote=$remote}|ConvertTo-Json -Depth 4 -Compress",
|
|
33
|
+
"@{ownerSid=$ownerSid;currentUserSid=$currentSid;daclPresent=($null -ne $dacl);aces=$aces;complete=$complete;remote=$remote}|Microsoft.PowerShell.Utility\\ConvertTo-Json -Depth 4 -Compress",
|
|
33
34
|
].join(";");
|
|
34
35
|
}
|
|
35
36
|
function parseWindowsAclFacts(parsed) {
|
|
@@ -89,7 +90,7 @@ export async function inspectWindowsOwner(params) {
|
|
|
89
90
|
}
|
|
90
91
|
catch (err) {
|
|
91
92
|
return {
|
|
92
|
-
error:
|
|
93
|
+
error: formatCaughtPermissionFailure(err),
|
|
93
94
|
errorDetail: getPermissionCommandFailure(err, command, performance.now() - startedAt),
|
|
94
95
|
errorCause: err,
|
|
95
96
|
};
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
// Executed only by the built-in Windows PowerShell/.NET host. The raw security
|
|
2
|
+
// descriptor never passes through CommonSecurityDescriptor's ACE normalization.
|
|
3
|
+
using System;
|
|
4
|
+
using System.Collections.Generic;
|
|
5
|
+
using System.ComponentModel;
|
|
6
|
+
using System.IO;
|
|
7
|
+
using System.Runtime.InteropServices;
|
|
8
|
+
using System.Security.AccessControl;
|
|
9
|
+
using System.Security.Principal;
|
|
10
|
+
using System.Text;
|
|
11
|
+
using Microsoft.Win32.SafeHandles;
|
|
12
|
+
|
|
13
|
+
public static partial class FsSafeWindowsBridge {
|
|
14
|
+
[StructLayout(LayoutKind.Sequential)] struct IoStatus { public IntPtr Status; public UIntPtr Information; }
|
|
15
|
+
[StructLayout(LayoutKind.Sequential)] struct UnicodeString { public ushort Length, MaximumLength; public IntPtr Buffer; }
|
|
16
|
+
[StructLayout(LayoutKind.Sequential)] struct ObjectAttributes {
|
|
17
|
+
public uint Length; public IntPtr RootDirectory, ObjectName; public uint Attributes;
|
|
18
|
+
public IntPtr SecurityDescriptor, SecurityQualityOfService;
|
|
19
|
+
}
|
|
20
|
+
[StructLayout(LayoutKind.Sequential)] struct FileInfo {
|
|
21
|
+
public uint Attributes, CreationLow, CreationHigh, AccessLow, AccessHigh, WriteLow, WriteHigh;
|
|
22
|
+
public uint Volume, SizeHigh, SizeLow, Links, IndexHigh, IndexLow;
|
|
23
|
+
}
|
|
24
|
+
[StructLayout(LayoutKind.Sequential)] struct FileId {
|
|
25
|
+
public ulong Volume; [MarshalAs(UnmanagedType.ByValArray, SizeConst=16)] public byte[] Id;
|
|
26
|
+
}
|
|
27
|
+
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode, ExactSpelling=true)] static extern SafeFileHandle CreateFileW(string path, uint access, uint share, IntPtr security, uint disposition, uint flags, IntPtr template);
|
|
28
|
+
[DllImport("kernel32.dll", SetLastError=true)] static extern IntPtr GetStdHandle(int standard);
|
|
29
|
+
[DllImport("kernel32.dll", SetLastError=true)] static extern uint GetFileType(SafeFileHandle handle);
|
|
30
|
+
[DllImport("kernel32.dll", SetLastError=true)] static extern bool GetFileInformationByHandle(SafeFileHandle handle, out FileInfo information);
|
|
31
|
+
[DllImport("kernel32.dll", SetLastError=true)] static extern bool GetFileInformationByHandleEx(SafeFileHandle handle, int kind, out FileId information, uint size);
|
|
32
|
+
[DllImport("kernel32.dll", SetLastError=true)] static extern bool SetFileInformationByHandle(SafeFileHandle handle, int kind, ref uint information, uint size);
|
|
33
|
+
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode, ExactSpelling=true)] static extern uint GetFinalPathNameByHandleW(SafeFileHandle handle, StringBuilder path, uint capacity, uint flags);
|
|
34
|
+
[DllImport("kernel32.dll")] static extern IntPtr LocalFree(IntPtr memory);
|
|
35
|
+
[DllImport("advapi32.dll")] static extern uint GetSecurityInfo(SafeFileHandle handle, int kind, uint sections, out IntPtr owner, out IntPtr group, out IntPtr dacl, out IntPtr sacl, out IntPtr descriptor);
|
|
36
|
+
[DllImport("advapi32.dll")] static extern uint GetSecurityDescriptorLength(IntPtr descriptor);
|
|
37
|
+
[DllImport("advapi32.dll", SetLastError=true)] static extern bool GetSecurityDescriptorDacl(IntPtr descriptor, out bool present, out IntPtr dacl, out bool defaulted);
|
|
38
|
+
[DllImport("advapi32.dll", SetLastError=true)] static extern bool GetSecurityDescriptorOwner(IntPtr descriptor, out IntPtr owner, out bool defaulted);
|
|
39
|
+
[DllImport("advapi32.dll")] static extern uint SetSecurityInfo(SafeFileHandle handle, int kind, uint sections, IntPtr owner, IntPtr group, IntPtr dacl, IntPtr sacl);
|
|
40
|
+
[DllImport("ntdll.dll")] static extern int NtQueryInformationFile(SafeFileHandle handle, out IoStatus io, [Out] byte[] information, uint length, int kind);
|
|
41
|
+
[DllImport("ntdll.dll")] static extern int NtCreateFile(out SafeFileHandle handle, uint access, ref ObjectAttributes attributes, out IoStatus io, IntPtr allocation, uint flags, uint share, uint disposition, uint options, IntPtr ea, uint eaLength);
|
|
42
|
+
[DllImport("ntdll.dll")] static extern uint RtlNtStatusToDosError(int status);
|
|
43
|
+
|
|
44
|
+
sealed class Failure : Exception {
|
|
45
|
+
public readonly string Code;
|
|
46
|
+
public Failure(string code, string message) : base(message) { Code=code; }
|
|
47
|
+
}
|
|
48
|
+
static Failure OsFailure(uint error, string operation, bool identity=false, bool fullIdentity=false) {
|
|
49
|
+
// Native security calls retain EACCES; shared handle identity calls use EPERM.
|
|
50
|
+
string code=error==5 ? (identity ? "EPERM" : "EACCES") : error==80 || error==183 ? "EEXIST" : error==2 || error==3 ? "ENOENT" :
|
|
51
|
+
identity && (error==112 || error==39) ? "ENOSPC" : identity && (error==32 || error==33) ? "EBUSY" :
|
|
52
|
+
fullIdentity && (error==1 || error==50 || error==87) ? "ENOTSUP" : "EIO";
|
|
53
|
+
return new Failure(code, operation+" failed with Windows error "+error);
|
|
54
|
+
}
|
|
55
|
+
static Failure CreateFailure(uint error) {
|
|
56
|
+
var failure=OsFailure(error,"create private directory",true);
|
|
57
|
+
return failure.Code=="EPERM" ? new Failure("EACCES",failure.Message) : failure;
|
|
58
|
+
}
|
|
59
|
+
static void Require(bool condition, string code, string message) {
|
|
60
|
+
if (!condition) throw new Failure(code,message);
|
|
61
|
+
}
|
|
62
|
+
static Dictionary<string,object> Row(params object[] fields) {
|
|
63
|
+
var result=new Dictionary<string,object>();
|
|
64
|
+
for(int i=0;i<fields.Length;i+=2) result.Add((string)fields[i],fields[i+1]);
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
static SafeFileHandle Open(string path, uint access, bool noFollow) {
|
|
68
|
+
var handle=CreateFileW(path,access,7,IntPtr.Zero,3,0x02000000u | (noFollow ? 0x00200000u : 0),IntPtr.Zero);
|
|
69
|
+
if(handle.IsInvalid) { uint error=(uint)Marshal.GetLastWin32Error(); handle.Dispose(); throw OsFailure(error,"open security handle"); }
|
|
70
|
+
return handle;
|
|
71
|
+
}
|
|
72
|
+
static FileInfo Information(SafeFileHandle handle) {
|
|
73
|
+
FileInfo info;
|
|
74
|
+
if(!GetFileInformationByHandle(handle,out info)) throw OsFailure((uint)Marshal.GetLastWin32Error(),"inspect handle identity",true);
|
|
75
|
+
return info;
|
|
76
|
+
}
|
|
77
|
+
static string Identity(SafeFileHandle handle) {
|
|
78
|
+
var info=Information(handle);
|
|
79
|
+
return info.Volume.ToString("x8")+":"+(((ulong)info.IndexHigh<<32)|info.IndexLow).ToString("x16");
|
|
80
|
+
}
|
|
81
|
+
static string DirectoryIdentity(SafeFileHandle handle, bool requireLocal=false) {
|
|
82
|
+
var info=Information(handle);
|
|
83
|
+
Require((info.Attributes&0x400)==0,"ELOOP","private directory must not be a reparse point");
|
|
84
|
+
Require((info.Attributes&0x10)!=0,"ENOTDIR","private directory handle must name a directory");
|
|
85
|
+
if(requireLocal) Require(IsLocal(handle),"ENOTSUP","private directories require a local filesystem");
|
|
86
|
+
return FullIdentity(handle);
|
|
87
|
+
}
|
|
88
|
+
static string FullIdentity(SafeFileHandle handle) {
|
|
89
|
+
FileId id;
|
|
90
|
+
if(!GetFileInformationByHandleEx(handle,18,out id,24)) throw OsFailure((uint)Marshal.GetLastWin32Error(),"inspect 128-bit file identity",true,true);
|
|
91
|
+
Require(id.Id!=null && id.Id.Length==16,"EIO","file identity is incomplete");
|
|
92
|
+
return id.Volume.ToString("x16")+":"+BitConverter.ToString(id.Id).Replace("-","").ToLowerInvariant();
|
|
93
|
+
}
|
|
94
|
+
static string FileIdentity(SafeFileHandle handle, uint expectedLinks) {
|
|
95
|
+
var info=Information(handle);
|
|
96
|
+
Require((info.Attributes&0x400)==0,"ELOOP","private file must not be a reparse point");
|
|
97
|
+
Require((info.Attributes&0x10)==0 && GetFileType(handle)==1,"EINVAL","private file must be a regular disk file");
|
|
98
|
+
Require(info.Links==expectedLinks,"EIO","private file link count changed");
|
|
99
|
+
return FullIdentity(handle);
|
|
100
|
+
}
|
|
101
|
+
static string FinalPath(SafeFileHandle handle, uint flags) {
|
|
102
|
+
int capacity=512;
|
|
103
|
+
for(int attempt=0;attempt<4;attempt++) {
|
|
104
|
+
var path=new StringBuilder(capacity);
|
|
105
|
+
uint length=GetFinalPathNameByHandleW(handle,path,(uint)capacity,flags);
|
|
106
|
+
if(length==0) throw OsFailure((uint)Marshal.GetLastWin32Error(),"inspect handle locality");
|
|
107
|
+
if(length<capacity) return path.ToString();
|
|
108
|
+
Require(length<32768,"EIO","final handle path exceeds the inspection budget");
|
|
109
|
+
capacity=(int)length+1;
|
|
110
|
+
}
|
|
111
|
+
throw new Failure("EIO","final handle path changed during inspection");
|
|
112
|
+
}
|
|
113
|
+
static bool IsVolumeGuidPath(string path) {
|
|
114
|
+
return path.Length>=49 && path.StartsWith(@"\\?\Volume{",StringComparison.OrdinalIgnoreCase) &&
|
|
115
|
+
path[47]=='}' && path[48]=='\\' &&
|
|
116
|
+
System.Text.RegularExpressions.Regex.IsMatch(path.Substring(11,36),@"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$");
|
|
117
|
+
}
|
|
118
|
+
static bool IsLocalFinalPath(string path) {
|
|
119
|
+
return !path.StartsWith(@"\\?\UNC\",StringComparison.Ordinal) &&
|
|
120
|
+
(!path.StartsWith(@"\\",StringComparison.Ordinal) || path.StartsWith(@"\\?\",StringComparison.Ordinal));
|
|
121
|
+
}
|
|
122
|
+
static bool IsLocal(SafeFileHandle handle) {
|
|
123
|
+
IoStatus io; var remote=new byte[]{255};
|
|
124
|
+
int status=NtQueryInformationFile(handle,out io,remote,1,51);
|
|
125
|
+
if(status==0 && io.Information.ToUInt64()==1 && remote[0]<=1) return remote[0]==0;
|
|
126
|
+
try {
|
|
127
|
+
if(IsVolumeGuidPath(FinalPath(handle,9))) return true;
|
|
128
|
+
} catch(Failure) { }
|
|
129
|
+
return IsLocalFinalPath(FinalPath(handle,0));
|
|
130
|
+
}
|
|
131
|
+
static string Sid(SecurityIdentifier sid) {
|
|
132
|
+
Require(sid!=null,"EIO","security descriptor SID is missing");
|
|
133
|
+
return sid.Value.ToLowerInvariant();
|
|
134
|
+
}
|
|
135
|
+
static Dictionary<string,object> Security(SafeFileHandle handle, bool requireLocality=true) {
|
|
136
|
+
IntPtr owner,group,dacl,sacl,descriptor;
|
|
137
|
+
uint error=GetSecurityInfo(handle,1,5,out owner,out group,out dacl,out sacl,out descriptor);
|
|
138
|
+
if(error!=0) throw OsFailure(error,"read owner and DACL");
|
|
139
|
+
try {
|
|
140
|
+
Require(descriptor!=IntPtr.Zero && owner!=IntPtr.Zero,"EIO","security descriptor is incomplete");
|
|
141
|
+
uint length=GetSecurityDescriptorLength(descriptor);
|
|
142
|
+
Require(length>=20 && length<=1024*1024,"EIO","security descriptor exceeds the inspection budget");
|
|
143
|
+
var bytes=new byte[length]; Marshal.Copy(descriptor,bytes,0,(int)length);
|
|
144
|
+
var raw=new RawSecurityDescriptor(bytes,0);
|
|
145
|
+
string ownerSid=Sid(raw.Owner), currentSid;
|
|
146
|
+
using(var current=WindowsIdentity.GetCurrent()) currentSid=Sid(current.User);
|
|
147
|
+
var aces=new List<object>(); var unsupported=new List<int>();
|
|
148
|
+
if(raw.DiscretionaryAcl!=null) foreach(GenericAce ace in raw.DiscretionaryAcl) {
|
|
149
|
+
var basic=ace as CommonAce;
|
|
150
|
+
if(basic==null || basic.IsCallback || (ace.AceType!=AceType.AccessAllowed && ace.AceType!=AceType.AccessDenied)) {
|
|
151
|
+
unsupported.Add((int)ace.AceType); continue;
|
|
152
|
+
}
|
|
153
|
+
byte flags=(byte)ace.AceFlags;
|
|
154
|
+
aces.Add(Row("sid",Sid(basic.SecurityIdentifier),"mask",unchecked((uint)basic.AccessMask),
|
|
155
|
+
"aceType",ace.AceType==AceType.AccessAllowed ? "allow" : "deny",
|
|
156
|
+
"flags",Row("raw",flags,"objectInherit",(flags&1)!=0,"containerInherit",(flags&2)!=0,
|
|
157
|
+
"noPropagateInherit",(flags&4)!=0,"inheritOnly",(flags&8)!=0,"inherited",(flags&16)!=0,
|
|
158
|
+
"successfulAccess",(flags&64)!=0,"failedAccess",(flags&128)!=0)));
|
|
159
|
+
}
|
|
160
|
+
bool isLocal;
|
|
161
|
+
try { isLocal=IsLocal(handle); }
|
|
162
|
+
catch(Failure) { if(requireLocality) throw; isLocal=false; }
|
|
163
|
+
return Row("ownerSid",ownerSid,"currentUserSid",currentSid,"daclPresent",raw.DiscretionaryAcl!=null,
|
|
164
|
+
"daclProtected",(raw.ControlFlags&ControlFlags.DiscretionaryAclProtected)!=0,
|
|
165
|
+
"isLocal",isLocal,"aceListComplete",unsupported.Count==0,"unsupportedAceTypes",unsupported.ToArray(),"aces",aces.ToArray());
|
|
166
|
+
} finally { if(descriptor!=IntPtr.Zero) LocalFree(descriptor); }
|
|
167
|
+
}
|
|
168
|
+
static byte[] PrivateSecurity(bool inherit=true) {
|
|
169
|
+
SecurityIdentifier current;
|
|
170
|
+
using(var identity=WindowsIdentity.GetCurrent()) current=identity.User;
|
|
171
|
+
var acl=new RawAcl(2,3);
|
|
172
|
+
var principals=new[]{current,new SecurityIdentifier("S-1-5-18"),new SecurityIdentifier("S-1-5-32-544")};
|
|
173
|
+
var flags=inherit ? AceFlags.ObjectInherit|AceFlags.ContainerInherit : AceFlags.None;
|
|
174
|
+
foreach(var principal in principals) acl.InsertAce(acl.Count,new CommonAce(flags,AceQualifier.AccessAllowed,0x1f01ff,principal,false,null));
|
|
175
|
+
var descriptor=new RawSecurityDescriptor(ControlFlags.DiscretionaryAclPresent|ControlFlags.DiscretionaryAclProtected|ControlFlags.SelfRelative,current,null,null,acl);
|
|
176
|
+
var bytes=new byte[descriptor.BinaryLength]; descriptor.GetBinaryForm(bytes,0); return bytes;
|
|
177
|
+
}
|
|
178
|
+
static SafeFileHandle CreateRelative(SafeFileHandle parent,string name) {
|
|
179
|
+
byte[] security=PrivateSecurity();
|
|
180
|
+
var pin=GCHandle.Alloc(security,GCHandleType.Pinned);
|
|
181
|
+
IntPtr nameBuffer=Marshal.StringToHGlobalUni(name), unicodeBuffer=IntPtr.Zero;
|
|
182
|
+
try {
|
|
183
|
+
int byteLength=checked(name.Length*2);
|
|
184
|
+
Require(byteLength>0 && byteLength<=65532,"EINVAL","private directory name is invalid");
|
|
185
|
+
var unicode=new UnicodeString { Length=(ushort)byteLength, MaximumLength=(ushort)(byteLength+2), Buffer=nameBuffer };
|
|
186
|
+
unicodeBuffer=Marshal.AllocHGlobal(Marshal.SizeOf(typeof(UnicodeString))); Marshal.StructureToPtr(unicode,unicodeBuffer,false);
|
|
187
|
+
var attributes=new ObjectAttributes { Length=(uint)Marshal.SizeOf(typeof(ObjectAttributes)),RootDirectory=parent.DangerousGetHandle(),
|
|
188
|
+
ObjectName=unicodeBuffer,Attributes=0x40|0x1000,SecurityDescriptor=pin.AddrOfPinnedObject() };
|
|
189
|
+
IoStatus io; SafeFileHandle created;
|
|
190
|
+
int status=NtCreateFile(out created,0x00130180,ref attributes,out io,IntPtr.Zero,0,7,2,0x00200021,IntPtr.Zero,0);
|
|
191
|
+
if(status<0) { if(created!=null) created.Dispose(); throw CreateFailure(RtlNtStatusToDosError(status)); }
|
|
192
|
+
return created;
|
|
193
|
+
} finally { if(unicodeBuffer!=IntPtr.Zero) Marshal.FreeHGlobal(unicodeBuffer); Marshal.FreeHGlobal(nameBuffer); pin.Free(); }
|
|
194
|
+
}
|
|
195
|
+
static void RequirePrivate(Dictionary<string,object> facts, bool requireProtected=true, bool requirePrivateChildren=false, bool allowAdministratorsOwner=false) {
|
|
196
|
+
string current=(string)facts["currentUserSid"], owner=(string)facts["ownerSid"];
|
|
197
|
+
Require((bool)facts["isLocal"] && (bool)facts["daclPresent"] && (!requireProtected || (bool)facts["daclProtected"]) &&
|
|
198
|
+
(bool)facts["aceListComplete"] && (owner==current || (allowAdministratorsOwner && owner=="s-1-5-32-544")),
|
|
199
|
+
"EACCES","private object security was not enforced");
|
|
200
|
+
foreach(Dictionary<string,object> ace in (object[])facts["aces"]) {
|
|
201
|
+
var flags=(Dictionary<string,object>)ace["flags"];
|
|
202
|
+
string sid=(string)ace["sid"];
|
|
203
|
+
if((string)ace["aceType"]=="deny" || sid==current || sid=="s-1-5-18" || sid=="s-1-5-32-544") continue;
|
|
204
|
+
if((bool)flags["inheritOnly"] && (!requirePrivateChildren || (!(bool)flags["objectInherit"] && !(bool)flags["containerInherit"]))) continue;
|
|
205
|
+
uint mask=(uint)ace["mask"];
|
|
206
|
+
Require((mask&0xd00d01dfu)==0,"EACCES","private object permits untrusted access");
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
static void SplitParent(string path, out string parentPath, out string name) {
|
|
210
|
+
// JavaScript has already admitted and resolved this spelling. Keep extended
|
|
211
|
+
// Win32 paths out of .NET Framework's separate pathname normalization.
|
|
212
|
+
int separator=path.LastIndexOf('\\');
|
|
213
|
+
Require(separator>0 && separator<path.Length-1,"EINVAL","private object must name a child");
|
|
214
|
+
parentPath=path.Substring(0,separator); name=path.Substring(separator+1);
|
|
215
|
+
if(parentPath.EndsWith(":")) parentPath+="\\";
|
|
216
|
+
}
|
|
217
|
+
static string ExpectedIdentity(string variable, bool required=true) {
|
|
218
|
+
string identity=Environment.GetEnvironmentVariable(variable) ?? "";
|
|
219
|
+
Require((!required && identity.Length==0) || System.Text.RegularExpressions.Regex.IsMatch(identity,@"\A[0-9a-f]{16}:[0-9a-f]{32}\z"),
|
|
220
|
+
"EINVAL","complete expected Windows identity is required");
|
|
221
|
+
return identity;
|
|
222
|
+
}
|
|
223
|
+
static void CheckParent(SafeFileHandle parent, string path, string identity) {
|
|
224
|
+
Require(DirectoryIdentity(parent,true)==identity,"EIO","retained private parent changed");
|
|
225
|
+
using(var named=Open(path,0x80,true)) Require(DirectoryIdentity(named,true)==identity,"EIO","private parent pathname changed");
|
|
226
|
+
}
|
|
227
|
+
static object InspectDirectory(string path) {
|
|
228
|
+
bool requirePrivate=Environment.GetEnvironmentVariable("FS_SAFE_WINDOWS_SECURITY_REQUIRE_PRIVATE")=="1";
|
|
229
|
+
using(var handle=Open(path,requirePrivate ? 0x00020080u : 0x80u,true)) {
|
|
230
|
+
string identity=DirectoryIdentity(handle,true);
|
|
231
|
+
if(requirePrivate) RequirePrivate(Security(handle),true,true);
|
|
232
|
+
return Row("identity",identity);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
static void ProtectFileSecurity(SafeFileHandle handle) {
|
|
236
|
+
byte[] security=PrivateSecurity(false);
|
|
237
|
+
var pin=GCHandle.Alloc(security,GCHandleType.Pinned);
|
|
238
|
+
try {
|
|
239
|
+
bool present,defaulted; IntPtr dacl,owner;
|
|
240
|
+
if(!GetSecurityDescriptorDacl(pin.AddrOfPinnedObject(),out present,out dacl,out defaulted)) {
|
|
241
|
+
throw OsFailure((uint)Marshal.GetLastWin32Error(),"inspect private file DACL");
|
|
242
|
+
}
|
|
243
|
+
Require(present && dacl!=IntPtr.Zero,"EIO","private file DACL is incomplete");
|
|
244
|
+
if(!GetSecurityDescriptorOwner(pin.AddrOfPinnedObject(),out owner,out defaulted)) {
|
|
245
|
+
throw OsFailure((uint)Marshal.GetLastWin32Error(),"inspect private file owner");
|
|
246
|
+
}
|
|
247
|
+
Require(owner!=IntPtr.Zero,"EIO","private file owner is incomplete");
|
|
248
|
+
uint error=SetSecurityInfo(handle,1,0x80000005u,owner,IntPtr.Zero,dacl,IntPtr.Zero);
|
|
249
|
+
if(error!=0) throw OsFailure(error,"protect private file owner and DACL");
|
|
250
|
+
} finally { pin.Free(); }
|
|
251
|
+
}
|
|
252
|
+
static void CheckFileAssociation(SafeFileHandle held, string path, string identity, uint links, SafeFileHandle parent, string parentPath, string parentIdentity) {
|
|
253
|
+
Require(FileIdentity(held,links)==identity,"EIO","retained private file changed");
|
|
254
|
+
using(var named=Open(path,0x80,true)) Require(FileIdentity(named,links)==identity,"EIO","private file pathname changed");
|
|
255
|
+
CheckParent(parent,parentPath,parentIdentity);
|
|
256
|
+
}
|
|
257
|
+
static object PrivateFile(string path, bool protect) {
|
|
258
|
+
string expectedParent=ExpectedIdentity("FS_SAFE_WINDOWS_SECURITY_PARENT_IDENTITY");
|
|
259
|
+
string expectedFile=protect ? "" : ExpectedIdentity("FS_SAFE_WINDOWS_SECURITY_FILE_IDENTITY");
|
|
260
|
+
uint links=1;
|
|
261
|
+
if(!protect) Require(uint.TryParse(Environment.GetEnvironmentVariable("FS_SAFE_WINDOWS_SECURITY_EXPECTED_LINKS"),
|
|
262
|
+
System.Globalization.NumberStyles.None,System.Globalization.CultureInfo.InvariantCulture,out links) && links>0,
|
|
263
|
+
"EINVAL","private file expected link count is invalid");
|
|
264
|
+
string parentPath,name; SplitParent(path,out parentPath,out name);
|
|
265
|
+
using(var held=new SafeFileHandle(GetStdHandle(-10),false)) {
|
|
266
|
+
Require(!held.IsInvalid,"EBADF","inherited file handle is unavailable");
|
|
267
|
+
string identity=FileIdentity(held,links);
|
|
268
|
+
if(!protect) Require(identity==expectedFile,"EIO","private file identity changed");
|
|
269
|
+
// Windows can use Administrators as the creating token's default owner.
|
|
270
|
+
// The inherited ACL must already be private: later changes cannot revoke
|
|
271
|
+
// an earlier reader handle.
|
|
272
|
+
RequirePrivate(Security(held),requireProtected:!protect,allowAdministratorsOwner:protect);
|
|
273
|
+
using(var parent=Open(parentPath,0x80,true)) {
|
|
274
|
+
CheckFileAssociation(held,path,identity,links,parent,parentPath,expectedParent);
|
|
275
|
+
if(protect) using(var writable=Open(path,0x000e0080,true)) {
|
|
276
|
+
Require(FileIdentity(writable,links)==identity,"EIO","private file changed before owner and DACL protection");
|
|
277
|
+
RequirePrivate(Security(writable),requireProtected:false,allowAdministratorsOwner:true);
|
|
278
|
+
CheckParent(parent,parentPath,expectedParent);
|
|
279
|
+
ProtectFileSecurity(writable);
|
|
280
|
+
RequirePrivate(Security(writable));
|
|
281
|
+
}
|
|
282
|
+
RequirePrivate(Security(held));
|
|
283
|
+
CheckFileAssociation(held,path,identity,links,parent,parentPath,expectedParent);
|
|
284
|
+
return Row("identity",identity);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
static object CreatePrivate(string path) {
|
|
289
|
+
string parentPath,name; SplitParent(path,out parentPath,out name);
|
|
290
|
+
string expectedParent=ExpectedIdentity("FS_SAFE_WINDOWS_SECURITY_PARENT_IDENTITY",false);
|
|
291
|
+
using(var parent=Open(parentPath,0xa4,true)) {
|
|
292
|
+
string parentId=DirectoryIdentity(parent,true);
|
|
293
|
+
if(expectedParent.Length!=0) Require(parentId==expectedParent,"EIO","private parent changed before creation");
|
|
294
|
+
using(var created=CreateRelative(parent,name)) {
|
|
295
|
+
try {
|
|
296
|
+
string createdId=DirectoryIdentity(created);
|
|
297
|
+
RequirePrivate(Security(created));
|
|
298
|
+
Require(DirectoryIdentity(parent)==parentId,"EIO","retained private parent changed");
|
|
299
|
+
using(var namedParent=Open(parentPath,0x80,true)) Require(DirectoryIdentity(namedParent,true)==parentId,"EIO","private parent pathname changed");
|
|
300
|
+
using(var named=Open(path,0x80,true)) {
|
|
301
|
+
Require(DirectoryIdentity(named,true)==createdId,"EIO","private directory pathname changed");
|
|
302
|
+
}
|
|
303
|
+
return Row("created",true,"identity",createdId);
|
|
304
|
+
} catch(Exception primary) {
|
|
305
|
+
uint flags=0x13;
|
|
306
|
+
if(!SetFileInformationByHandle(created,21,ref flags,4)) {
|
|
307
|
+
var failure=primary as Failure;
|
|
308
|
+
throw new Failure(failure==null ? "EIO" : failure.Code,
|
|
309
|
+
(failure==null ? "private directory validation failed" : failure.Message)+
|
|
310
|
+
"; owned-handle cleanup failed with Windows error "+Marshal.GetLastWin32Error());
|
|
311
|
+
}
|
|
312
|
+
throw;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
public static object Execute(string operation,string path) {
|
|
318
|
+
try {
|
|
319
|
+
object result;
|
|
320
|
+
if(operation=="create") result=CreatePrivate(path);
|
|
321
|
+
else if(operation=="directory") result=InspectDirectory(path);
|
|
322
|
+
else if(operation=="protect-file" || operation=="verify-file") result=PrivateFile(path,operation=="protect-file");
|
|
323
|
+
else if(operation=="descriptor") {
|
|
324
|
+
using(var handle=new SafeFileHandle(GetStdHandle(-10),false)) {
|
|
325
|
+
Require(!handle.IsInvalid,"EBADF","inherited file handle is unavailable");
|
|
326
|
+
result=Row("identity",Identity(handle),"security",Security(handle));
|
|
327
|
+
}
|
|
328
|
+
} else if(operation=="path") {
|
|
329
|
+
// Raw reporting retains ACL facts when locality is unknown; admission remains strict.
|
|
330
|
+
using(var handle=Open(path,0x00020080,false)) result=Security(handle,false);
|
|
331
|
+
} else throw new Failure("EINVAL","unknown Windows security operation");
|
|
332
|
+
return Row("ok",true,"result",result);
|
|
333
|
+
} catch(Failure error) { return Row("ok",false,"code",error.Code,"message",error.Message); }
|
|
334
|
+
catch(Exception) { return Row("ok",false,"code","EIO","message","Windows security descriptor processing failed"); }
|
|
335
|
+
}
|
|
336
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[Parameter(Mandatory = $true)]
|
|
3
|
+
[ValidateSet('path', 'descriptor', 'create', 'directory', 'protect-file', 'verify-file')]
|
|
4
|
+
[string] $Operation
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
$ErrorActionPreference = 'Stop'
|
|
8
|
+
$ProgressPreference = 'SilentlyContinue'
|
|
9
|
+
# Avoid scanning unrelated installed modules before loading system cmdlets.
|
|
10
|
+
$env:PSModulePath = [IO.Path]::Combine($PSHOME, 'Modules')
|
|
11
|
+
[Console]::OutputEncoding = [Text.UTF8Encoding]::new($false)
|
|
12
|
+
|
|
13
|
+
Microsoft.PowerShell.Utility\Add-Type -LiteralPath ([IO.Path]::Combine($PSScriptRoot, 'windows-security-bridge.cs'))
|
|
14
|
+
$targetPath = [Environment]::GetEnvironmentVariable('FS_SAFE_WINDOWS_SECURITY_PATH')
|
|
15
|
+
[FsSafeWindowsBridge]::Execute($Operation, $targetPath) | Microsoft.PowerShell.Utility\ConvertTo-Json -Depth 8 -Compress
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { NativeWindowsDescriptorSecurityFacts, NativeWindowsSecurityFacts } from "./native-binding.js";
|
|
2
|
+
/** Cleanup must preserve stages still reachable by an unsettled command. */
|
|
3
|
+
export declare function hasUnsettledWindowsSecurityCommand(error: unknown): boolean;
|
|
4
|
+
export declare function readWindowsSecurityFactsCommand(targetPath: string): NativeWindowsSecurityFacts;
|
|
5
|
+
export declare function inspectWindowsDescriptorCommand(fd: number): Promise<NativeWindowsDescriptorSecurityFacts>;
|
|
6
|
+
export declare function createPrivateWindowsDirectoryCommand(targetPath: string, expectedParentIdentity?: string): Promise<{
|
|
7
|
+
identity: string;
|
|
8
|
+
}>;
|
|
9
|
+
export declare function createPrivateWindowsDirectoryCommandSync(targetPath: string, expectedParentIdentity?: string): {
|
|
10
|
+
identity: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function inspectWindowsDirectoryCommandSync(targetPath: string, requirePrivate: boolean): {
|
|
13
|
+
identity: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function inspectWindowsDirectoryCommand(targetPath: string, requirePrivate: boolean): Promise<{
|
|
16
|
+
identity: string;
|
|
17
|
+
}>;
|
|
18
|
+
export declare function protectPrivateWindowsFileCommandSync(fd: number, targetPath: string, expectedParentIdentity: string): {
|
|
19
|
+
identity: string;
|
|
20
|
+
};
|
|
21
|
+
export declare function protectPrivateWindowsFileCommand(fd: number, targetPath: string, expectedParentIdentity: string): Promise<{
|
|
22
|
+
identity: string;
|
|
23
|
+
}>;
|
|
24
|
+
export declare function verifyPrivateWindowsFileCommandSync(fd: number, targetPath: string, expectedFileIdentity: string, expectedParentIdentity: string, expectedLinks?: number): void;
|
|
25
|
+
export declare function verifyPrivateWindowsFileCommand(fd: number, targetPath: string, expectedFileIdentity: string, expectedParentIdentity: string, expectedLinks?: number): Promise<void>;
|
|
26
|
+
//# sourceMappingURL=windows-security-command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windows-security-command.d.ts","sourceRoot":"","sources":["../src/windows-security-command.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oCAAoC,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AA2J5G,4EAA4E;AAC5E,wBAAgB,kCAAkC,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAqB1E;AAwHD,wBAAgB,+BAA+B,CAAC,UAAU,EAAE,MAAM,GAAG,0BAA0B,CAE9F;AAED,wBAAsB,+BAA+B,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,oCAAoC,CAAC,CAM/G;AAED,wBAAsB,oCAAoC,CAAC,UAAU,EAAE,MAAM,EAAE,sBAAsB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAE7I;AAED,wBAAgB,wCAAwC,CAAC,UAAU,EAAE,MAAM,EAAE,sBAAsB,CAAC,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAElI;AAYD,wBAAgB,kCAAkC,CAAC,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAEpH;AAED,wBAAsB,8BAA8B,CAAC,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAE/H;AAED,wBAAgB,oCAAoC,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAGzI;AAED,wBAAsB,gCAAgC,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAGpJ;AAED,wBAAgB,mCAAmC,CACjD,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,EAAE,aAAa,SAAI,GAC9G,IAAI,CAMN;AAED,wBAAsB,+BAA+B,CACnD,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,EAAE,aAAa,SAAI,GAC9G,OAAO,CAAC,IAAI,CAAC,CAMf"}
|