@sysid/sandbox-runtime-improved 0.0.56-sysid.1 → 0.0.64-sysid.1
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/README.md +106 -4
- package/dist/cli.js +12 -18
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/sandbox/credential-mask-files.d.ts +157 -0
- package/dist/sandbox/credential-mask-files.d.ts.map +1 -0
- package/dist/sandbox/credential-mask-files.js +298 -0
- package/dist/sandbox/credential-mask-files.js.map +1 -0
- package/dist/sandbox/credential-sentinel.d.ts +72 -0
- package/dist/sandbox/credential-sentinel.d.ts.map +1 -0
- package/dist/sandbox/credential-sentinel.js +130 -0
- package/dist/sandbox/credential-sentinel.js.map +1 -0
- package/dist/sandbox/domain-pattern.d.ts +36 -0
- package/dist/sandbox/domain-pattern.d.ts.map +1 -0
- package/dist/sandbox/domain-pattern.js +60 -0
- package/dist/sandbox/domain-pattern.js.map +1 -0
- package/dist/sandbox/http-proxy.d.ts +33 -2
- package/dist/sandbox/http-proxy.d.ts.map +1 -1
- package/dist/sandbox/http-proxy.js +30 -2
- package/dist/sandbox/http-proxy.js.map +1 -1
- package/dist/sandbox/linux-sandbox-utils.d.ts +20 -0
- package/dist/sandbox/linux-sandbox-utils.d.ts.map +1 -1
- package/dist/sandbox/linux-sandbox-utils.js +134 -65
- package/dist/sandbox/linux-sandbox-utils.js.map +1 -1
- package/dist/sandbox/linux-violation-monitor.d.ts +48 -0
- package/dist/sandbox/linux-violation-monitor.d.ts.map +1 -0
- package/dist/sandbox/linux-violation-monitor.js +156 -0
- package/dist/sandbox/linux-violation-monitor.js.map +1 -0
- package/dist/sandbox/listen-in-range.d.ts +12 -0
- package/dist/sandbox/listen-in-range.d.ts.map +1 -0
- package/dist/sandbox/listen-in-range.js +43 -0
- package/dist/sandbox/listen-in-range.js.map +1 -0
- package/dist/sandbox/macos-sandbox-utils.d.ts +11 -0
- package/dist/sandbox/macos-sandbox-utils.d.ts.map +1 -1
- package/dist/sandbox/macos-sandbox-utils.js +72 -17
- package/dist/sandbox/macos-sandbox-utils.js.map +1 -1
- package/dist/sandbox/mitm-ca.d.ts +69 -2
- package/dist/sandbox/mitm-ca.d.ts.map +1 -1
- package/dist/sandbox/mitm-ca.js +188 -16
- package/dist/sandbox/mitm-ca.js.map +1 -1
- package/dist/sandbox/mitm-leaf.d.ts +1 -1
- package/dist/sandbox/mitm-leaf.d.ts.map +1 -1
- package/dist/sandbox/mitm-leaf.js +26 -19
- package/dist/sandbox/mitm-leaf.js.map +1 -1
- package/dist/sandbox/mux-proxy.d.ts +59 -0
- package/dist/sandbox/mux-proxy.d.ts.map +1 -0
- package/dist/sandbox/mux-proxy.js +159 -0
- package/dist/sandbox/mux-proxy.js.map +1 -0
- package/dist/sandbox/request-filter.d.ts +11 -1
- package/dist/sandbox/request-filter.d.ts.map +1 -1
- package/dist/sandbox/request-filter.js.map +1 -1
- package/dist/sandbox/sandbox-config.d.ts +461 -41
- package/dist/sandbox/sandbox-config.d.ts.map +1 -1
- package/dist/sandbox/sandbox-config.js +342 -26
- package/dist/sandbox/sandbox-config.js.map +1 -1
- package/dist/sandbox/sandbox-manager.d.ts +5 -1
- package/dist/sandbox/sandbox-manager.d.ts.map +1 -1
- package/dist/sandbox/sandbox-manager.js +697 -211
- package/dist/sandbox/sandbox-manager.js.map +1 -1
- package/dist/sandbox/sandbox-schemas.d.ts +15 -0
- package/dist/sandbox/sandbox-schemas.d.ts.map +1 -1
- package/dist/sandbox/sandbox-utils.d.ts +55 -6
- package/dist/sandbox/sandbox-utils.d.ts.map +1 -1
- package/dist/sandbox/sandbox-utils.js +143 -30
- package/dist/sandbox/sandbox-utils.js.map +1 -1
- package/dist/sandbox/socks-proxy.d.ts +11 -5
- package/dist/sandbox/socks-proxy.d.ts.map +1 -1
- package/dist/sandbox/socks-proxy.js +13 -81
- package/dist/sandbox/socks-proxy.js.map +1 -1
- package/dist/sandbox/tls-terminate-proxy.d.ts +2 -2
- package/dist/sandbox/tls-terminate-proxy.d.ts.map +1 -1
- package/dist/sandbox/tls-terminate-proxy.js +31 -5
- package/dist/sandbox/tls-terminate-proxy.js.map +1 -1
- package/dist/sandbox/windows-sandbox-utils.d.ts +508 -116
- package/dist/sandbox/windows-sandbox-utils.d.ts.map +1 -1
- package/dist/sandbox/windows-sandbox-utils.js +721 -209
- package/dist/sandbox/windows-sandbox-utils.js.map +1 -1
- package/dist/utils/shell-quote.d.ts +27 -0
- package/dist/utils/shell-quote.d.ts.map +1 -0
- package/dist/utils/shell-quote.js +47 -0
- package/dist/utils/shell-quote.js.map +1 -0
- package/package.json +7 -6
- package/vendor/seccomp/arm64/apply-seccomp +0 -0
- package/vendor/seccomp/build.ts +8 -30
- package/vendor/seccomp/x64/apply-seccomp +0 -0
- package/vendor/srt-win/build.ts +21 -0
- package/dist/vendor/seccomp/Dockerfile.build +0 -6
- package/dist/vendor/seccomp/arm64/apply-seccomp +0 -0
- package/dist/vendor/seccomp/build.ts +0 -91
- package/dist/vendor/seccomp/x64/apply-seccomp +0 -0
- package/dist/vendor/seccomp-src/apply-seccomp.c +0 -280
- package/dist/vendor/seccomp-src/seccomp-unix-block.c +0 -148
- package/dist/vendor/srt-win/Cargo.lock +0 -361
- package/dist/vendor/srt-win/Cargo.toml +0 -46
- package/dist/vendor/srt-win/ci/cleanup.ps1 +0 -49
- package/dist/vendor/srt-win/ci/smoke-exec.ps1 +0 -446
- package/dist/vendor/srt-win/ci/smoke.ps1 +0 -307
- package/dist/vendor/srt-win/src/job.rs +0 -102
- package/dist/vendor/srt-win/src/launch.rs +0 -732
- package/dist/vendor/srt-win/src/lib.rs +0 -20
- package/dist/vendor/srt-win/src/main.rs +0 -669
- package/dist/vendor/srt-win/src/self_protect.rs +0 -169
- package/dist/vendor/srt-win/src/sid.rs +0 -296
- package/dist/vendor/srt-win/src/token.rs +0 -341
- package/dist/vendor/srt-win/src/util.rs +0 -42
- package/dist/vendor/srt-win/src/wfp.rs +0 -992
- package/dist/vendor/srt-win/src/winsta.rs +0 -209
- package/dist/vendor/srt-win/tests/sd_access_check_matrix.rs +0 -259
- package/vendor/seccomp-src/apply-seccomp.c +0 -280
- package/vendor/seccomp-src/seccomp-unix-block.c +0 -148
- package/vendor/srt-win/Cargo.lock +0 -361
- package/vendor/srt-win/Cargo.toml +0 -46
- package/vendor/srt-win/ci/cleanup.ps1 +0 -49
- package/vendor/srt-win/ci/smoke-exec.ps1 +0 -446
- package/vendor/srt-win/ci/smoke.ps1 +0 -307
- package/vendor/srt-win/src/job.rs +0 -102
- package/vendor/srt-win/src/launch.rs +0 -732
- package/vendor/srt-win/src/lib.rs +0 -20
- package/vendor/srt-win/src/main.rs +0 -669
- package/vendor/srt-win/src/self_protect.rs +0 -169
- package/vendor/srt-win/src/sid.rs +0 -296
- package/vendor/srt-win/src/token.rs +0 -341
- package/vendor/srt-win/src/util.rs +0 -42
- package/vendor/srt-win/src/wfp.rs +0 -992
- package/vendor/srt-win/src/winsta.rs +0 -209
- package/vendor/srt-win/tests/sd_access_check_matrix.rs +0 -259
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
//! Broker self-protection: rewrite the broker process's own DACL
|
|
2
|
-
//! so the sandbox child cannot `OpenProcess(broker_pid,
|
|
3
|
-
//! PROCESS_VM_*|CREATE_THREAD)`.
|
|
4
|
-
//!
|
|
5
|
-
//! The DACL is replaced with an explicit ALLOW list scoped to SIDs
|
|
6
|
-
//! the sandbox child does NOT have enabled in its token:
|
|
7
|
-
//!
|
|
8
|
-
//! - `<group_sid>` — child has it deny-only
|
|
9
|
-
//! - SYSTEM (S-1-5-18) — child doesn't carry it
|
|
10
|
-
//! - BUILTIN\Admins — child has it deny-only
|
|
11
|
-
//! - OWNER RIGHTS = 0 — suppresses the owner's implicit
|
|
12
|
-
//! `READ_CONTROL|WRITE_DAC`, which would otherwise let the
|
|
13
|
-
//! child (same user = owner) `WRITE_DAC` the broker
|
|
14
|
-
//!
|
|
15
|
-
//! `PROTECTED_DACL_SECURITY_INFORMATION` is required: without it
|
|
16
|
-
//! the inherited "user has full access to their own process" ACE
|
|
17
|
-
//! survives and the rewrite is a no-op for same-user children.
|
|
18
|
-
//!
|
|
19
|
-
//! Documented residual: another non-sandbox process belonging to
|
|
20
|
-
//! the same user (e.g. Explorer) still has the group enabled and
|
|
21
|
-
//! can therefore open the broker. That's intentional — the threat
|
|
22
|
-
//! model is the sandbox child, not the rest of the user's session.
|
|
23
|
-
|
|
24
|
-
use anyhow::{anyhow, Context, Result};
|
|
25
|
-
use std::ffi::c_void;
|
|
26
|
-
use std::mem::size_of;
|
|
27
|
-
use windows::core::PWSTR;
|
|
28
|
-
use windows::Win32::Foundation::HANDLE;
|
|
29
|
-
use windows::Win32::Security::Authorization::{
|
|
30
|
-
ConvertSecurityDescriptorToStringSecurityDescriptorW, GetSecurityInfo,
|
|
31
|
-
SetSecurityInfo, SDDL_REVISION_1, SE_KERNEL_OBJECT,
|
|
32
|
-
};
|
|
33
|
-
use windows::Win32::Security::{
|
|
34
|
-
AddAccessAllowedAce, GetLengthSid, InitializeAcl, ACL, ACL_REVISION,
|
|
35
|
-
DACL_SECURITY_INFORMATION, PROTECTED_DACL_SECURITY_INFORMATION,
|
|
36
|
-
};
|
|
37
|
-
use windows::Win32::System::Threading::{GetCurrentProcess, PROCESS_ALL_ACCESS};
|
|
38
|
-
|
|
39
|
-
use crate::sid::LocalPsid;
|
|
40
|
-
|
|
41
|
-
/// Owner-Rights well-known SID (`S-1-3-4`). An ALLOW ACE with mask 0
|
|
42
|
-
/// on this SID overrides the implicit `READ_CONTROL|WRITE_DAC` that
|
|
43
|
-
/// the object's owner otherwise gets.
|
|
44
|
-
const SID_OWNER_RIGHTS: &str = "S-1-3-4";
|
|
45
|
-
const SID_SYSTEM: &str = "S-1-5-18";
|
|
46
|
-
const SID_BUILTIN_ADMINS: &str = "S-1-5-32-544";
|
|
47
|
-
|
|
48
|
-
/// Rewrite the current process's DACL to the broker-only pattern.
|
|
49
|
-
/// Idempotent — safe to call once per `srt-win exec` invocation.
|
|
50
|
-
pub fn install_broker_dacl(group_sid: &str) -> Result<()> {
|
|
51
|
-
// RAII over `ConvertStringSidToSidW` → freed via `LocalFree` on
|
|
52
|
-
// drop.
|
|
53
|
-
let group = LocalPsid::from_string(group_sid)
|
|
54
|
-
.with_context(|| format!("parse group SID '{group_sid}'"))?;
|
|
55
|
-
let system = LocalPsid::from_string(SID_SYSTEM)?;
|
|
56
|
-
let admins = LocalPsid::from_string(SID_BUILTIN_ADMINS)?;
|
|
57
|
-
let owner_rights = LocalPsid::from_string(SID_OWNER_RIGHTS)?;
|
|
58
|
-
|
|
59
|
-
// (sid, mask) — first three get full access; OWNER_RIGHTS gets
|
|
60
|
-
// nothing. CI passes `group_sid == BUILTIN\Administrators`;
|
|
61
|
-
// dedup so we don't write a redundant ACE.
|
|
62
|
-
let mut aces: Vec<(windows::Win32::Security::PSID, u32)> = vec![
|
|
63
|
-
(group.as_psid(), PROCESS_ALL_ACCESS.0),
|
|
64
|
-
(system.as_psid(), PROCESS_ALL_ACCESS.0),
|
|
65
|
-
];
|
|
66
|
-
if !group_sid.eq_ignore_ascii_case(SID_BUILTIN_ADMINS) {
|
|
67
|
-
aces.push((admins.as_psid(), PROCESS_ALL_ACCESS.0));
|
|
68
|
-
}
|
|
69
|
-
aces.push((owner_rights.as_psid(), 0u32));
|
|
70
|
-
|
|
71
|
-
// ACL size = header + Σ(ACE fixed prefix + SID body). The fixed
|
|
72
|
-
// prefix of an ACCESS_ALLOWED_ACE is 8 bytes (Header 4 + Mask 4);
|
|
73
|
-
// `SidStart` is the first DWORD of the SID, so total per-ACE =
|
|
74
|
-
// 8 + GetLengthSid.
|
|
75
|
-
const ACE_FIXED: usize = 8;
|
|
76
|
-
let mut total = size_of::<ACL>();
|
|
77
|
-
for (s, _) in &aces {
|
|
78
|
-
let len = unsafe { GetLengthSid(*s) } as usize;
|
|
79
|
-
if len == 0 {
|
|
80
|
-
return Err(anyhow!("GetLengthSid returned 0"));
|
|
81
|
-
}
|
|
82
|
-
total += ACE_FIXED + len;
|
|
83
|
-
}
|
|
84
|
-
total = (total + 3) & !3; // DWORD-align
|
|
85
|
-
|
|
86
|
-
let mut buf = vec![0u8; total];
|
|
87
|
-
let acl = buf.as_mut_ptr() as *mut ACL;
|
|
88
|
-
unsafe {
|
|
89
|
-
InitializeAcl(acl, total as u32, ACL_REVISION)
|
|
90
|
-
.context("InitializeAcl")?;
|
|
91
|
-
for (s, mask) in &aces {
|
|
92
|
-
AddAccessAllowedAce(acl, ACL_REVISION, *mask, *s)
|
|
93
|
-
.context("AddAccessAllowedAce")?;
|
|
94
|
-
}
|
|
95
|
-
// PROTECTED strips inherited ACEs — without it the user
|
|
96
|
-
// SID's default "full access to own process" inherited
|
|
97
|
-
// grant fires and the rewrite is a no-op.
|
|
98
|
-
let r = SetSecurityInfo(
|
|
99
|
-
GetCurrentProcess(),
|
|
100
|
-
SE_KERNEL_OBJECT,
|
|
101
|
-
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,
|
|
102
|
-
None,
|
|
103
|
-
None,
|
|
104
|
-
Some(acl),
|
|
105
|
-
None,
|
|
106
|
-
);
|
|
107
|
-
if r.is_err() {
|
|
108
|
-
return Err(anyhow!(
|
|
109
|
-
"SetSecurityInfo(broker process DACL): {r:?}"
|
|
110
|
-
));
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
// `buf` can drop here — `SetSecurityInfo` copies the ACL into
|
|
114
|
-
// the kernel object's SECURITY_DESCRIPTOR.
|
|
115
|
-
|
|
116
|
-
// Diagnostic: read back and dump the DACL as SDDL so CI can
|
|
117
|
-
// confirm exactly what's on the broker process. Gated on
|
|
118
|
-
// SANDBOX_RUNTIME_WIN_DEBUG — production callers (batch 03:
|
|
119
|
-
// one exec per user command) don't want a stderr line per
|
|
120
|
-
// command. CI sets the env var so E6 still records the SDDL.
|
|
121
|
-
if std::env::var_os("SANDBOX_RUNTIME_WIN_DEBUG").is_some() {
|
|
122
|
-
match read_self_dacl_sddl() {
|
|
123
|
-
Some(sddl) => eprintln!(
|
|
124
|
-
"srt-win: self-protect applied (DACL: {sddl})"
|
|
125
|
-
),
|
|
126
|
-
None => eprintln!("srt-win: self-protect applied"),
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
Ok(())
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/// Best-effort read of the current process's DACL as an SDDL
|
|
133
|
-
/// string. Returns `None` on any failure rather than erroring —
|
|
134
|
-
/// this is diagnostic only.
|
|
135
|
-
fn read_self_dacl_sddl() -> Option<String> {
|
|
136
|
-
use crate::util::{from_pwstr, local_free};
|
|
137
|
-
use windows::Win32::Security::PSECURITY_DESCRIPTOR;
|
|
138
|
-
unsafe {
|
|
139
|
-
let mut psd = PSECURITY_DESCRIPTOR::default();
|
|
140
|
-
let r = GetSecurityInfo(
|
|
141
|
-
HANDLE(GetCurrentProcess().0),
|
|
142
|
-
SE_KERNEL_OBJECT,
|
|
143
|
-
DACL_SECURITY_INFORMATION,
|
|
144
|
-
None,
|
|
145
|
-
None,
|
|
146
|
-
None,
|
|
147
|
-
None,
|
|
148
|
-
Some(&mut psd),
|
|
149
|
-
);
|
|
150
|
-
if r.is_err() || psd.0.is_null() {
|
|
151
|
-
return None;
|
|
152
|
-
}
|
|
153
|
-
let mut s = PWSTR::null();
|
|
154
|
-
let ok = ConvertSecurityDescriptorToStringSecurityDescriptorW(
|
|
155
|
-
psd,
|
|
156
|
-
SDDL_REVISION_1,
|
|
157
|
-
DACL_SECURITY_INFORMATION,
|
|
158
|
-
&mut s,
|
|
159
|
-
None,
|
|
160
|
-
);
|
|
161
|
-
local_free(psd.0);
|
|
162
|
-
if ok.is_err() {
|
|
163
|
-
return None;
|
|
164
|
-
}
|
|
165
|
-
let out = from_pwstr(s);
|
|
166
|
-
local_free(s.0 as *mut c_void);
|
|
167
|
-
Some(out)
|
|
168
|
-
}
|
|
169
|
-
}
|
|
@@ -1,296 +0,0 @@
|
|
|
1
|
-
//! PSID ↔ string-SID helpers and token-membership queries.
|
|
2
|
-
//!
|
|
3
|
-
//! All `PSID` values returned by `ConvertStringSidToSidW` are
|
|
4
|
-
//! heap-allocated by the OS via `LocalAlloc` and **must** be freed
|
|
5
|
-
//! with `LocalFree` — never `FreeSid`, which is only valid for SIDs
|
|
6
|
-
//! built by `AllocateAndInitializeSid`. The `LocalPsid` RAII wrapper
|
|
7
|
-
//! enforces that.
|
|
8
|
-
|
|
9
|
-
use anyhow::{anyhow, Context, Result};
|
|
10
|
-
use std::ffi::c_void;
|
|
11
|
-
use windows::core::PWSTR;
|
|
12
|
-
use windows::Win32::Foundation::{CloseHandle, LocalFree, HANDLE, HLOCAL};
|
|
13
|
-
use windows::Win32::Security::Authorization::{
|
|
14
|
-
ConvertSidToStringSidW, ConvertStringSidToSidW,
|
|
15
|
-
};
|
|
16
|
-
use windows::Win32::Security::{
|
|
17
|
-
EqualSid, GetTokenInformation, LookupAccountNameW, LookupAccountSidW,
|
|
18
|
-
PSID, SID_NAME_USE, TokenGroups, TokenUser, TOKEN_GROUPS, TOKEN_QUERY,
|
|
19
|
-
TOKEN_USER,
|
|
20
|
-
};
|
|
21
|
-
use windows::Win32::System::SystemServices::{
|
|
22
|
-
SE_GROUP_ENABLED, SE_GROUP_USE_FOR_DENY_ONLY,
|
|
23
|
-
};
|
|
24
|
-
use windows::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
|
|
25
|
-
|
|
26
|
-
use crate::util::{from_pwstr, local_free, pcwstr, wstr};
|
|
27
|
-
|
|
28
|
-
/// RAII wrapper for a `PSID` returned by `ConvertStringSidToSidW`.
|
|
29
|
-
/// Freed via `LocalFree` on drop.
|
|
30
|
-
pub struct LocalPsid(PSID);
|
|
31
|
-
|
|
32
|
-
impl LocalPsid {
|
|
33
|
-
/// Parse a string SID like `"S-1-5-32-544"`.
|
|
34
|
-
pub fn from_string(sid_str: &str) -> Result<Self> {
|
|
35
|
-
let mut sid = PSID::default();
|
|
36
|
-
let w = wstr(sid_str);
|
|
37
|
-
unsafe {
|
|
38
|
-
ConvertStringSidToSidW(pcwstr(&w), &mut sid).map_err(|e| {
|
|
39
|
-
anyhow!("ConvertStringSidToSidW({sid_str}): {e}")
|
|
40
|
-
})?;
|
|
41
|
-
}
|
|
42
|
-
Ok(Self(sid))
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
pub fn as_psid(&self) -> PSID {
|
|
46
|
-
self.0
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
impl Drop for LocalPsid {
|
|
51
|
-
fn drop(&mut self) {
|
|
52
|
-
if !self.0 .0.is_null() {
|
|
53
|
-
unsafe {
|
|
54
|
-
let _ = LocalFree(Some(HLOCAL(self.0 .0)));
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/// Stringify a `PSID`. Used for serialisation and logging.
|
|
61
|
-
pub fn psid_to_string(sid: PSID) -> Result<String> {
|
|
62
|
-
let mut p = PWSTR::null();
|
|
63
|
-
unsafe {
|
|
64
|
-
ConvertSidToStringSidW(sid, &mut p)
|
|
65
|
-
.map_err(|e| anyhow!("ConvertSidToStringSidW: {e}"))?;
|
|
66
|
-
}
|
|
67
|
-
let s = from_pwstr(p);
|
|
68
|
-
local_free(p.0 as *mut c_void);
|
|
69
|
-
Ok(s)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/// Outcome of a SID → account reverse lookup.
|
|
73
|
-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
74
|
-
pub enum SidExistence {
|
|
75
|
-
/// `LookupAccountSidW` resolved the SID to an account.
|
|
76
|
-
Mapped,
|
|
77
|
-
/// `LookupAccountSidW` returned `ERROR_NONE_MAPPED` — well-formed
|
|
78
|
-
/// SID with no corresponding account. Treat as "absent".
|
|
79
|
-
Unmapped,
|
|
80
|
-
/// Lookup failed for a transient reason (e.g. domain controller
|
|
81
|
-
/// unreachable). Caller should fall through to other checks
|
|
82
|
-
/// rather than report absent.
|
|
83
|
-
Unknown,
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/// Reverse-lookup: does any account correspond to `sid_str`?
|
|
87
|
-
/// Used by `group status --group-sid` so a typo'd SID is reported as
|
|
88
|
-
/// `absent` rather than `created-not-on-token`.
|
|
89
|
-
pub fn sid_account_exists(sid_str: &str) -> Result<SidExistence> {
|
|
90
|
-
use windows::Win32::Foundation::{
|
|
91
|
-
GetLastError, ERROR_INSUFFICIENT_BUFFER, ERROR_NONE_MAPPED,
|
|
92
|
-
};
|
|
93
|
-
let psid = LocalPsid::from_string(sid_str)?;
|
|
94
|
-
unsafe {
|
|
95
|
-
let mut cch_name: u32 = 0;
|
|
96
|
-
let mut cch_dom: u32 = 0;
|
|
97
|
-
let mut use_: SID_NAME_USE = SID_NAME_USE::default();
|
|
98
|
-
// Sizing call — we only care about the error code.
|
|
99
|
-
let r = LookupAccountSidW(
|
|
100
|
-
windows::core::PCWSTR::null(),
|
|
101
|
-
psid.as_psid(),
|
|
102
|
-
None,
|
|
103
|
-
&mut cch_name,
|
|
104
|
-
None,
|
|
105
|
-
&mut cch_dom,
|
|
106
|
-
&mut use_,
|
|
107
|
-
);
|
|
108
|
-
if r.is_ok() {
|
|
109
|
-
// Shouldn't happen with zero-length buffers, but treat as
|
|
110
|
-
// mapped if it does.
|
|
111
|
-
return Ok(SidExistence::Mapped);
|
|
112
|
-
}
|
|
113
|
-
match GetLastError() {
|
|
114
|
-
ERROR_INSUFFICIENT_BUFFER => Ok(SidExistence::Mapped),
|
|
115
|
-
ERROR_NONE_MAPPED => Ok(SidExistence::Unmapped),
|
|
116
|
-
// RPC_S_SERVER_UNAVAILABLE, ERROR_TRUSTED_RELATIONSHIP_FAILURE,
|
|
117
|
-
// etc. — don't claim absence on a transient lookup failure.
|
|
118
|
-
_ => Ok(SidExistence::Unknown),
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/// Resolve an account name (local or domain) to a string SID via
|
|
124
|
-
/// `LookupAccountNameW` with a NULL system name (local SAM first,
|
|
125
|
-
/// then domain). Errors if the name is not found.
|
|
126
|
-
pub fn lookup_account_sid(name: &str) -> Result<String> {
|
|
127
|
-
unsafe {
|
|
128
|
-
let mut cb_sid: u32 = 0;
|
|
129
|
-
let mut cch_dom: u32 = 0;
|
|
130
|
-
let mut use_: SID_NAME_USE = SID_NAME_USE::default();
|
|
131
|
-
let name_w = wstr(name);
|
|
132
|
-
// Sizing call. Expected to fail with ERROR_INSUFFICIENT_BUFFER.
|
|
133
|
-
let _ = LookupAccountNameW(
|
|
134
|
-
windows::core::PCWSTR::null(),
|
|
135
|
-
pcwstr(&name_w),
|
|
136
|
-
None,
|
|
137
|
-
&mut cb_sid,
|
|
138
|
-
None,
|
|
139
|
-
&mut cch_dom,
|
|
140
|
-
&mut use_,
|
|
141
|
-
);
|
|
142
|
-
if cb_sid == 0 {
|
|
143
|
-
return Err(anyhow!(
|
|
144
|
-
"LookupAccountNameW({name}): account not found"
|
|
145
|
-
));
|
|
146
|
-
}
|
|
147
|
-
let mut sid_buf = vec![0u8; cb_sid as usize];
|
|
148
|
-
let mut dom_buf = vec![0u16; cch_dom.max(1) as usize];
|
|
149
|
-
LookupAccountNameW(
|
|
150
|
-
windows::core::PCWSTR::null(),
|
|
151
|
-
pcwstr(&name_w),
|
|
152
|
-
Some(PSID(sid_buf.as_mut_ptr() as *mut c_void)),
|
|
153
|
-
&mut cb_sid,
|
|
154
|
-
Some(PWSTR(dom_buf.as_mut_ptr())),
|
|
155
|
-
&mut cch_dom,
|
|
156
|
-
&mut use_,
|
|
157
|
-
)
|
|
158
|
-
.map_err(|e| anyhow!("LookupAccountNameW({name}): {e}"))?;
|
|
159
|
-
psid_to_string(PSID(sid_buf.as_mut_ptr() as *mut c_void))
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/// String SID of the current process token's user.
|
|
164
|
-
pub fn current_user_sid() -> Result<String> {
|
|
165
|
-
unsafe {
|
|
166
|
-
let mut tok = HANDLE::default();
|
|
167
|
-
OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut tok)
|
|
168
|
-
.context("OpenProcessToken")?;
|
|
169
|
-
let mut len = 0u32;
|
|
170
|
-
let _ = GetTokenInformation(tok, TokenUser, None, 0, &mut len);
|
|
171
|
-
let mut buf = vec![0u8; len as usize];
|
|
172
|
-
let r = GetTokenInformation(
|
|
173
|
-
tok,
|
|
174
|
-
TokenUser,
|
|
175
|
-
Some(buf.as_mut_ptr() as *mut c_void),
|
|
176
|
-
len,
|
|
177
|
-
&mut len,
|
|
178
|
-
);
|
|
179
|
-
let _ = CloseHandle(tok);
|
|
180
|
-
r.context("GetTokenInformation(TokenUser)")?;
|
|
181
|
-
let tu = &*(buf.as_ptr() as *const TOKEN_USER);
|
|
182
|
-
psid_to_string(tu.User.Sid)
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/// State of a SID inside the current process's `TokenGroups`.
|
|
187
|
-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
188
|
-
pub enum GroupState {
|
|
189
|
-
/// Present with `SE_GROUP_ENABLED` set. Broker tokens look like
|
|
190
|
-
/// this once the user has logged out + back in after group
|
|
191
|
-
/// creation.
|
|
192
|
-
Enabled,
|
|
193
|
-
/// Present with `SE_GROUP_USE_FOR_DENY_ONLY` set. A sandbox child
|
|
194
|
-
/// token looks like this; a broker should never see it.
|
|
195
|
-
DenyOnly,
|
|
196
|
-
/// Present but neither enabled nor deny-only. Unexpected.
|
|
197
|
-
Present,
|
|
198
|
-
/// Not in `TokenGroups` at all. Typical immediately after group
|
|
199
|
-
/// creation, before the user re-logs-in.
|
|
200
|
-
Absent,
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/// How does `target_sid` appear in the current process token's
|
|
204
|
-
/// `TokenGroups`?
|
|
205
|
-
pub fn group_state_for_self(target_sid: &str) -> Result<GroupState> {
|
|
206
|
-
let target = LocalPsid::from_string(target_sid)?;
|
|
207
|
-
unsafe {
|
|
208
|
-
let mut tok = HANDLE::default();
|
|
209
|
-
OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut tok)
|
|
210
|
-
.context("OpenProcessToken")?;
|
|
211
|
-
let mut len = 0u32;
|
|
212
|
-
let _ = GetTokenInformation(tok, TokenGroups, None, 0, &mut len);
|
|
213
|
-
let mut buf = vec![0u8; len as usize];
|
|
214
|
-
let r = GetTokenInformation(
|
|
215
|
-
tok,
|
|
216
|
-
TokenGroups,
|
|
217
|
-
Some(buf.as_mut_ptr() as *mut c_void),
|
|
218
|
-
len,
|
|
219
|
-
&mut len,
|
|
220
|
-
);
|
|
221
|
-
let _ = CloseHandle(tok);
|
|
222
|
-
r.context("GetTokenInformation(TokenGroups)")?;
|
|
223
|
-
let tg = &*(buf.as_ptr() as *const TOKEN_GROUPS);
|
|
224
|
-
let arr = std::slice::from_raw_parts(
|
|
225
|
-
tg.Groups.as_ptr(),
|
|
226
|
-
tg.GroupCount as usize,
|
|
227
|
-
);
|
|
228
|
-
for g in arr {
|
|
229
|
-
if EqualSid(target.as_psid(), g.Sid).is_ok() {
|
|
230
|
-
let attrs = g.Attributes as i32;
|
|
231
|
-
if attrs & SE_GROUP_USE_FOR_DENY_ONLY != 0 {
|
|
232
|
-
return Ok(GroupState::DenyOnly);
|
|
233
|
-
}
|
|
234
|
-
if attrs & SE_GROUP_ENABLED != 0 {
|
|
235
|
-
return Ok(GroupState::Enabled);
|
|
236
|
-
}
|
|
237
|
-
return Ok(GroupState::Present);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
Ok(GroupState::Absent)
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
#[cfg(test)]
|
|
245
|
-
mod tests {
|
|
246
|
-
use super::*;
|
|
247
|
-
|
|
248
|
-
#[test]
|
|
249
|
-
fn psid_string_round_trip() {
|
|
250
|
-
// Well-known Everyone SID.
|
|
251
|
-
let p = LocalPsid::from_string("S-1-1-0").expect("from_string");
|
|
252
|
-
let s = psid_to_string(p.as_psid()).expect("to_string");
|
|
253
|
-
assert_eq!(s, "S-1-1-0");
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
#[test]
|
|
257
|
-
fn lookup_builtin_users() {
|
|
258
|
-
// BUILTIN\Users exists on every Windows install.
|
|
259
|
-
let s = lookup_account_sid("BUILTIN\\Users").expect("lookup");
|
|
260
|
-
assert_eq!(s, "S-1-5-32-545");
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
#[test]
|
|
264
|
-
fn lookup_missing_account_errors() {
|
|
265
|
-
let r = lookup_account_sid("no-such-group-srt-win-test");
|
|
266
|
-
assert!(r.is_err());
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
#[test]
|
|
270
|
-
fn sid_account_exists_for_well_known() {
|
|
271
|
-
assert_eq!(
|
|
272
|
-
sid_account_exists("S-1-5-32-545").unwrap(),
|
|
273
|
-
SidExistence::Mapped
|
|
274
|
-
);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
#[test]
|
|
278
|
-
fn sid_account_exists_unmapped_for_bogus() {
|
|
279
|
-
// Well-formed but maps to nothing on any machine.
|
|
280
|
-
assert_eq!(
|
|
281
|
-
sid_account_exists("S-1-5-21-1-2-3-9999999").unwrap(),
|
|
282
|
-
SidExistence::Unmapped
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
#[test]
|
|
287
|
-
fn sid_account_exists_errors_on_malformed() {
|
|
288
|
-
assert!(sid_account_exists("not-a-sid").is_err());
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
#[test]
|
|
292
|
-
fn current_user_sid_is_nonempty() {
|
|
293
|
-
let s = current_user_sid().expect("current_user_sid");
|
|
294
|
-
assert!(s.starts_with("S-1-"), "got {s}");
|
|
295
|
-
}
|
|
296
|
-
}
|