@sysid/sandbox-runtime-improved 0.0.56-sysid.1 → 0.0.61-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.
Files changed (116) hide show
  1. package/README.md +43 -2
  2. package/dist/index.d.ts +3 -3
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +1 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/sandbox/credential-mask-files.d.ts +72 -0
  7. package/dist/sandbox/credential-mask-files.d.ts.map +1 -0
  8. package/dist/sandbox/credential-mask-files.js +146 -0
  9. package/dist/sandbox/credential-mask-files.js.map +1 -0
  10. package/dist/sandbox/credential-sentinel.d.ts +72 -0
  11. package/dist/sandbox/credential-sentinel.d.ts.map +1 -0
  12. package/dist/sandbox/credential-sentinel.js +130 -0
  13. package/dist/sandbox/credential-sentinel.js.map +1 -0
  14. package/dist/sandbox/domain-pattern.d.ts +36 -0
  15. package/dist/sandbox/domain-pattern.d.ts.map +1 -0
  16. package/dist/sandbox/domain-pattern.js +60 -0
  17. package/dist/sandbox/domain-pattern.js.map +1 -0
  18. package/dist/sandbox/http-proxy.d.ts +32 -1
  19. package/dist/sandbox/http-proxy.d.ts.map +1 -1
  20. package/dist/sandbox/http-proxy.js +10 -2
  21. package/dist/sandbox/http-proxy.js.map +1 -1
  22. package/dist/sandbox/linux-sandbox-utils.d.ts +15 -0
  23. package/dist/sandbox/linux-sandbox-utils.d.ts.map +1 -1
  24. package/dist/sandbox/linux-sandbox-utils.js +106 -47
  25. package/dist/sandbox/linux-sandbox-utils.js.map +1 -1
  26. package/dist/sandbox/listen-in-range.d.ts +12 -0
  27. package/dist/sandbox/listen-in-range.d.ts.map +1 -0
  28. package/dist/sandbox/listen-in-range.js +43 -0
  29. package/dist/sandbox/listen-in-range.js.map +1 -0
  30. package/dist/sandbox/macos-sandbox-utils.d.ts +11 -0
  31. package/dist/sandbox/macos-sandbox-utils.d.ts.map +1 -1
  32. package/dist/sandbox/macos-sandbox-utils.js +69 -14
  33. package/dist/sandbox/macos-sandbox-utils.js.map +1 -1
  34. package/dist/sandbox/mitm-ca.d.ts +18 -2
  35. package/dist/sandbox/mitm-ca.d.ts.map +1 -1
  36. package/dist/sandbox/mitm-ca.js +49 -9
  37. package/dist/sandbox/mitm-ca.js.map +1 -1
  38. package/dist/sandbox/mitm-leaf.d.ts.map +1 -1
  39. package/dist/sandbox/mitm-leaf.js +24 -6
  40. package/dist/sandbox/mitm-leaf.js.map +1 -1
  41. package/dist/sandbox/mux-proxy.d.ts +59 -0
  42. package/dist/sandbox/mux-proxy.d.ts.map +1 -0
  43. package/dist/sandbox/mux-proxy.js +159 -0
  44. package/dist/sandbox/mux-proxy.js.map +1 -0
  45. package/dist/sandbox/request-filter.d.ts +11 -1
  46. package/dist/sandbox/request-filter.d.ts.map +1 -1
  47. package/dist/sandbox/request-filter.js.map +1 -1
  48. package/dist/sandbox/sandbox-config.d.ts +254 -25
  49. package/dist/sandbox/sandbox-config.d.ts.map +1 -1
  50. package/dist/sandbox/sandbox-config.js +200 -13
  51. package/dist/sandbox/sandbox-config.js.map +1 -1
  52. package/dist/sandbox/sandbox-manager.d.ts +4 -0
  53. package/dist/sandbox/sandbox-manager.d.ts.map +1 -1
  54. package/dist/sandbox/sandbox-manager.js +601 -194
  55. package/dist/sandbox/sandbox-manager.js.map +1 -1
  56. package/dist/sandbox/sandbox-schemas.d.ts +15 -0
  57. package/dist/sandbox/sandbox-schemas.d.ts.map +1 -1
  58. package/dist/sandbox/sandbox-utils.d.ts +42 -6
  59. package/dist/sandbox/sandbox-utils.d.ts.map +1 -1
  60. package/dist/sandbox/sandbox-utils.js +115 -27
  61. package/dist/sandbox/sandbox-utils.js.map +1 -1
  62. package/dist/sandbox/socks-proxy.d.ts +11 -5
  63. package/dist/sandbox/socks-proxy.d.ts.map +1 -1
  64. package/dist/sandbox/socks-proxy.js +13 -81
  65. package/dist/sandbox/socks-proxy.js.map +1 -1
  66. package/dist/sandbox/tls-terminate-proxy.d.ts +2 -2
  67. package/dist/sandbox/tls-terminate-proxy.d.ts.map +1 -1
  68. package/dist/sandbox/tls-terminate-proxy.js +31 -5
  69. package/dist/sandbox/tls-terminate-proxy.js.map +1 -1
  70. package/dist/sandbox/windows-sandbox-utils.d.ts +347 -11
  71. package/dist/sandbox/windows-sandbox-utils.d.ts.map +1 -1
  72. package/dist/sandbox/windows-sandbox-utils.js +437 -45
  73. package/dist/sandbox/windows-sandbox-utils.js.map +1 -1
  74. package/package.json +7 -4
  75. package/vendor/seccomp/build.ts +8 -30
  76. package/vendor/srt-win/build.ts +21 -0
  77. package/dist/vendor/seccomp/Dockerfile.build +0 -6
  78. package/dist/vendor/seccomp/arm64/apply-seccomp +0 -0
  79. package/dist/vendor/seccomp/build.ts +0 -91
  80. package/dist/vendor/seccomp/x64/apply-seccomp +0 -0
  81. package/dist/vendor/seccomp-src/apply-seccomp.c +0 -280
  82. package/dist/vendor/seccomp-src/seccomp-unix-block.c +0 -148
  83. package/dist/vendor/srt-win/Cargo.lock +0 -361
  84. package/dist/vendor/srt-win/Cargo.toml +0 -46
  85. package/dist/vendor/srt-win/ci/cleanup.ps1 +0 -49
  86. package/dist/vendor/srt-win/ci/smoke-exec.ps1 +0 -446
  87. package/dist/vendor/srt-win/ci/smoke.ps1 +0 -307
  88. package/dist/vendor/srt-win/src/job.rs +0 -102
  89. package/dist/vendor/srt-win/src/launch.rs +0 -732
  90. package/dist/vendor/srt-win/src/lib.rs +0 -20
  91. package/dist/vendor/srt-win/src/main.rs +0 -669
  92. package/dist/vendor/srt-win/src/self_protect.rs +0 -169
  93. package/dist/vendor/srt-win/src/sid.rs +0 -296
  94. package/dist/vendor/srt-win/src/token.rs +0 -341
  95. package/dist/vendor/srt-win/src/util.rs +0 -42
  96. package/dist/vendor/srt-win/src/wfp.rs +0 -992
  97. package/dist/vendor/srt-win/src/winsta.rs +0 -209
  98. package/dist/vendor/srt-win/tests/sd_access_check_matrix.rs +0 -259
  99. package/vendor/seccomp-src/apply-seccomp.c +0 -280
  100. package/vendor/seccomp-src/seccomp-unix-block.c +0 -148
  101. package/vendor/srt-win/Cargo.lock +0 -361
  102. package/vendor/srt-win/Cargo.toml +0 -46
  103. package/vendor/srt-win/ci/cleanup.ps1 +0 -49
  104. package/vendor/srt-win/ci/smoke-exec.ps1 +0 -446
  105. package/vendor/srt-win/ci/smoke.ps1 +0 -307
  106. package/vendor/srt-win/src/job.rs +0 -102
  107. package/vendor/srt-win/src/launch.rs +0 -732
  108. package/vendor/srt-win/src/lib.rs +0 -20
  109. package/vendor/srt-win/src/main.rs +0 -669
  110. package/vendor/srt-win/src/self_protect.rs +0 -169
  111. package/vendor/srt-win/src/sid.rs +0 -296
  112. package/vendor/srt-win/src/token.rs +0 -341
  113. package/vendor/srt-win/src/util.rs +0 -42
  114. package/vendor/srt-win/src/wfp.rs +0 -992
  115. package/vendor/srt-win/src/winsta.rs +0 -209
  116. 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
- }