@sysid/sandbox-runtime-improved 0.0.52-sysid.1 → 0.0.55-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 +5 -2
- package/dist/cli.js +91 -7
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/sandbox/http-proxy.d.ts +7 -0
- package/dist/sandbox/http-proxy.d.ts.map +1 -1
- package/dist/sandbox/http-proxy.js +20 -0
- package/dist/sandbox/http-proxy.js.map +1 -1
- package/dist/sandbox/linux-sandbox-utils.d.ts +2 -0
- package/dist/sandbox/linux-sandbox-utils.d.ts.map +1 -1
- package/dist/sandbox/linux-sandbox-utils.js +107 -46
- package/dist/sandbox/linux-sandbox-utils.js.map +1 -1
- package/dist/sandbox/macos-sandbox-utils.d.ts +3 -0
- package/dist/sandbox/macos-sandbox-utils.d.ts.map +1 -1
- package/dist/sandbox/macos-sandbox-utils.js +32 -3
- package/dist/sandbox/macos-sandbox-utils.js.map +1 -1
- package/dist/sandbox/mitm-leaf.d.ts.map +1 -1
- package/dist/sandbox/mitm-leaf.js +18 -5
- package/dist/sandbox/mitm-leaf.js.map +1 -1
- package/dist/sandbox/sandbox-config.d.ts +63 -2
- package/dist/sandbox/sandbox-config.d.ts.map +1 -1
- package/dist/sandbox/sandbox-config.js +52 -2
- package/dist/sandbox/sandbox-config.js.map +1 -1
- package/dist/sandbox/sandbox-manager.d.ts +5 -0
- package/dist/sandbox/sandbox-manager.d.ts.map +1 -1
- package/dist/sandbox/sandbox-manager.js +208 -44
- package/dist/sandbox/sandbox-manager.js.map +1 -1
- package/dist/sandbox/sandbox-schemas.d.ts +9 -3
- package/dist/sandbox/sandbox-schemas.d.ts.map +1 -1
- package/dist/sandbox/sandbox-utils.d.ts +1 -1
- package/dist/sandbox/sandbox-utils.d.ts.map +1 -1
- package/dist/sandbox/sandbox-utils.js +46 -18
- package/dist/sandbox/sandbox-utils.js.map +1 -1
- package/dist/sandbox/socks-proxy.d.ts +6 -0
- package/dist/sandbox/socks-proxy.d.ts.map +1 -1
- package/dist/sandbox/socks-proxy.js +11 -0
- package/dist/sandbox/socks-proxy.js.map +1 -1
- package/dist/sandbox/tls-terminate-proxy.js +18 -4
- package/dist/sandbox/tls-terminate-proxy.js.map +1 -1
- package/dist/sandbox/windows-sandbox-utils.d.ts +224 -0
- package/dist/sandbox/windows-sandbox-utils.d.ts.map +1 -0
- package/dist/sandbox/windows-sandbox-utils.js +433 -0
- package/dist/sandbox/windows-sandbox-utils.js.map +1 -0
- package/dist/vendor/srt-win/Cargo.lock +361 -0
- package/dist/vendor/srt-win/Cargo.toml +46 -0
- package/dist/vendor/srt-win/ci/cleanup.ps1 +49 -0
- package/dist/vendor/srt-win/ci/smoke-exec.ps1 +446 -0
- package/dist/vendor/srt-win/ci/smoke.ps1 +307 -0
- package/dist/vendor/srt-win/src/job.rs +102 -0
- package/dist/vendor/srt-win/src/launch.rs +732 -0
- package/dist/vendor/srt-win/src/lib.rs +20 -0
- package/dist/vendor/srt-win/src/main.rs +669 -0
- package/dist/vendor/srt-win/src/self_protect.rs +169 -0
- package/dist/vendor/srt-win/src/sid.rs +296 -0
- package/dist/vendor/srt-win/src/token.rs +341 -0
- package/dist/vendor/srt-win/src/util.rs +42 -0
- package/dist/vendor/srt-win/src/wfp.rs +992 -0
- package/dist/vendor/srt-win/src/winsta.rs +209 -0
- package/dist/vendor/srt-win/tests/sd_access_check_matrix.rs +259 -0
- package/package.json +2 -2
- package/vendor/srt-win/Cargo.lock +361 -0
- package/vendor/srt-win/Cargo.toml +46 -0
- package/vendor/srt-win/ci/cleanup.ps1 +49 -0
- package/vendor/srt-win/ci/smoke-exec.ps1 +446 -0
- package/vendor/srt-win/ci/smoke.ps1 +307 -0
- package/vendor/srt-win/src/job.rs +102 -0
- package/vendor/srt-win/src/launch.rs +732 -0
- package/vendor/srt-win/src/lib.rs +20 -0
- package/vendor/srt-win/src/main.rs +669 -0
- package/vendor/srt-win/src/self_protect.rs +169 -0
- package/vendor/srt-win/src/sid.rs +296 -0
- package/vendor/srt-win/src/token.rs +341 -0
- package/vendor/srt-win/src/util.rs +42 -0
- package/vendor/srt-win/src/wfp.rs +992 -0
- package/vendor/srt-win/src/winsta.rs +209 -0
- package/vendor/srt-win/tests/sd_access_check_matrix.rs +259 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
//! Restricted-token construction for the deny-only-group sandbox.
|
|
2
|
+
//!
|
|
3
|
+
//! Token shape:
|
|
4
|
+
//! - `SidsToDisable = [<group_sid>, BUILTIN\Administrators]` — flips
|
|
5
|
+
//! them to `SE_GROUP_USE_FOR_DENY_ONLY` without touching the
|
|
6
|
+
//! restricting list.
|
|
7
|
+
//! - `LUA_TOKEN` flag — token reads as a normal limited-user token
|
|
8
|
+
//! to NT components.
|
|
9
|
+
//! - All privileges deleted except `SeChangeNotifyPrivilege`.
|
|
10
|
+
//! - Integrity Level = Medium (same as a normal user process).
|
|
11
|
+
//! - No `RestrictingSids` array — that breaks Schannel/LSA RPC.
|
|
12
|
+
//!
|
|
13
|
+
//! WFP's `ALE_USER_ID` AccessCheck honours
|
|
14
|
+
//! `SE_GROUP_USE_FOR_DENY_ONLY`, so an SDDL ACE
|
|
15
|
+
//! `(A;;CC;;;<group_sid>)` matches only when the group is *enabled*
|
|
16
|
+
//! — i.e. on the broker, never on the sandbox child.
|
|
17
|
+
|
|
18
|
+
use anyhow::{anyhow, Context, Result};
|
|
19
|
+
use std::ffi::c_void;
|
|
20
|
+
use std::mem::size_of;
|
|
21
|
+
use windows::Win32::Foundation::{HANDLE, LUID};
|
|
22
|
+
use windows::Win32::Security::{
|
|
23
|
+
AddAccessAllowedAce, AllocateAndInitializeSid, CreateRestrictedToken,
|
|
24
|
+
DuplicateTokenEx, FreeSid, GetLengthSid, GetTokenInformation,
|
|
25
|
+
InitializeAcl, LookupPrivilegeValueW, SecurityImpersonation,
|
|
26
|
+
SetTokenInformation, TokenDefaultDacl, TokenGroups, TokenIntegrityLevel,
|
|
27
|
+
TokenPrimary, TokenPrivileges, ACL, ACL_REVISION, LUA_TOKEN,
|
|
28
|
+
LUID_AND_ATTRIBUTES, PSID, SID_AND_ATTRIBUTES, SID_IDENTIFIER_AUTHORITY,
|
|
29
|
+
TOKEN_ALL_ACCESS, TOKEN_DEFAULT_DACL, TOKEN_GROUPS,
|
|
30
|
+
TOKEN_INFORMATION_CLASS, TOKEN_MANDATORY_LABEL, TOKEN_PRIVILEGES,
|
|
31
|
+
};
|
|
32
|
+
use windows::Win32::System::SystemServices::SE_GROUP_LOGON_ID;
|
|
33
|
+
use windows::Win32::System::Threading::{
|
|
34
|
+
GetCurrentProcess, OpenProcessToken,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
use crate::sid::LocalPsid;
|
|
38
|
+
use crate::util::{pcwstr, wstr};
|
|
39
|
+
|
|
40
|
+
/// Medium IL (`SECURITY_MANDATORY_MEDIUM_RID`). The sandbox child
|
|
41
|
+
/// runs at Medium — same as normal user processes — so Schannel /
|
|
42
|
+
/// LSA / registry edge cases that fire at Low IL don't apply.
|
|
43
|
+
pub const IL_MEDIUM: u32 = 0x2000;
|
|
44
|
+
|
|
45
|
+
/// `BUILTIN\Administrators`. Always added to `SidsToDisable` so an
|
|
46
|
+
/// elevated broker still produces a non-admin child.
|
|
47
|
+
const SID_BUILTIN_ADMINS: &str = "S-1-5-32-544";
|
|
48
|
+
|
|
49
|
+
/// `SE_GROUP_INTEGRITY` attribute (winnt.h). Required on the
|
|
50
|
+
/// `TOKEN_MANDATORY_LABEL.Label.Attributes` field.
|
|
51
|
+
const SE_GROUP_INTEGRITY: u32 = 0x0000_0020;
|
|
52
|
+
|
|
53
|
+
/// Open this process's primary token with full access.
|
|
54
|
+
pub fn open_self_token() -> Result<HANDLE> {
|
|
55
|
+
unsafe {
|
|
56
|
+
let mut h = HANDLE::default();
|
|
57
|
+
OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &mut h)
|
|
58
|
+
.context("OpenProcessToken")?;
|
|
59
|
+
Ok(h)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// Build the deny-only-group restricted token from `base` (the
|
|
64
|
+
/// broker's own token). `group_sid` is the discriminator group that
|
|
65
|
+
/// gets flipped deny-only. Returns a non-primary token; the caller
|
|
66
|
+
/// duplicates to primary via [`to_primary`].
|
|
67
|
+
pub fn make_sandbox_token(base: HANDLE, group_sid: &str) -> Result<HANDLE> {
|
|
68
|
+
// SIDs to disable. `LocalPsid` is RAII over
|
|
69
|
+
// `ConvertStringSidToSidW` and frees with `LocalFree` on drop —
|
|
70
|
+
// the donor used raw `PSID` + `FreeSid` here, which is the wrong
|
|
71
|
+
// free fn for that allocator.
|
|
72
|
+
//
|
|
73
|
+
// CI uses `group_sid == BUILTIN\Administrators` (the only SID
|
|
74
|
+
// that's both on the runner's token without a logout AND
|
|
75
|
+
// discriminator-shaped). Dedup so `CreateRestrictedToken`
|
|
76
|
+
// doesn't see the same SID twice in `SidsToDisable`.
|
|
77
|
+
let group = LocalPsid::from_string(group_sid)
|
|
78
|
+
.with_context(|| format!("parse group SID '{group_sid}'"))?;
|
|
79
|
+
let admins;
|
|
80
|
+
let mut psids: Vec<PSID> = vec![group.as_psid()];
|
|
81
|
+
if !group_sid.eq_ignore_ascii_case(SID_BUILTIN_ADMINS) {
|
|
82
|
+
admins = LocalPsid::from_string(SID_BUILTIN_ADMINS)?;
|
|
83
|
+
psids.push(admins.as_psid());
|
|
84
|
+
}
|
|
85
|
+
let disable: Vec<SID_AND_ATTRIBUTES> = psids
|
|
86
|
+
.into_iter()
|
|
87
|
+
.map(|s| SID_AND_ATTRIBUTES { Sid: s, Attributes: 0 })
|
|
88
|
+
.collect();
|
|
89
|
+
|
|
90
|
+
// Privileges to delete: everything in `base` except
|
|
91
|
+
// SeChangeNotifyPrivilege.
|
|
92
|
+
let to_delete = privileges_except(base, &["SeChangeNotifyPrivilege"])?;
|
|
93
|
+
|
|
94
|
+
let mut out = HANDLE::default();
|
|
95
|
+
unsafe {
|
|
96
|
+
CreateRestrictedToken(
|
|
97
|
+
base,
|
|
98
|
+
LUA_TOKEN,
|
|
99
|
+
Some(&disable),
|
|
100
|
+
if to_delete.is_empty() { None } else { Some(&to_delete) },
|
|
101
|
+
None, // RestrictingSids — intentionally empty
|
|
102
|
+
&mut out,
|
|
103
|
+
)
|
|
104
|
+
.with_context(|| {
|
|
105
|
+
format!("CreateRestrictedToken(disable=[Admins,{group_sid}])")
|
|
106
|
+
})?;
|
|
107
|
+
}
|
|
108
|
+
// `admins` / `group` LocalPsid drop here → LocalFree.
|
|
109
|
+
|
|
110
|
+
// RAII-own `out` so a `?` from set_il/set_default_dacl below
|
|
111
|
+
// closes it. Local — token.rs is the only module that holds
|
|
112
|
+
// raw HANDLEs across fallible calls outside launch.rs.
|
|
113
|
+
struct Guard(HANDLE);
|
|
114
|
+
impl Drop for Guard {
|
|
115
|
+
fn drop(&mut self) {
|
|
116
|
+
unsafe {
|
|
117
|
+
let _ = windows::Win32::Foundation::CloseHandle(self.0);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
let guard = Guard(out);
|
|
122
|
+
set_il(guard.0, IL_MEDIUM)?;
|
|
123
|
+
set_default_dacl(guard.0, base)?;
|
|
124
|
+
// Disarm and return the raw handle to the caller.
|
|
125
|
+
let h = guard.0;
|
|
126
|
+
std::mem::forget(guard);
|
|
127
|
+
Ok(h)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/// Duplicate to a primary token (`CreateProcessAsUserW` requires a
|
|
131
|
+
/// primary).
|
|
132
|
+
pub fn to_primary(token: HANDLE) -> Result<HANDLE> {
|
|
133
|
+
unsafe {
|
|
134
|
+
let mut out = HANDLE::default();
|
|
135
|
+
DuplicateTokenEx(
|
|
136
|
+
token,
|
|
137
|
+
TOKEN_ALL_ACCESS,
|
|
138
|
+
None,
|
|
139
|
+
SecurityImpersonation,
|
|
140
|
+
TokenPrimary,
|
|
141
|
+
&mut out,
|
|
142
|
+
)
|
|
143
|
+
.context("DuplicateTokenEx(primary)")?;
|
|
144
|
+
Ok(out)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/// Set the token's integrity level via a mandatory-label SID built
|
|
149
|
+
/// with `AllocateAndInitializeSid` (so [`FreeSid`] is the correct
|
|
150
|
+
/// release fn — the one place in this crate where it is).
|
|
151
|
+
fn set_il(tok: HANDLE, rid: u32) -> Result<()> {
|
|
152
|
+
unsafe {
|
|
153
|
+
let ml_auth = SID_IDENTIFIER_AUTHORITY {
|
|
154
|
+
Value: [0, 0, 0, 0, 0, 16],
|
|
155
|
+
};
|
|
156
|
+
let mut sid = PSID::default();
|
|
157
|
+
AllocateAndInitializeSid(
|
|
158
|
+
&ml_auth, 1, rid, 0, 0, 0, 0, 0, 0, 0, &mut sid,
|
|
159
|
+
)
|
|
160
|
+
.context("AllocateAndInitializeSid(mandatory label)")?;
|
|
161
|
+
let tml = TOKEN_MANDATORY_LABEL {
|
|
162
|
+
Label: SID_AND_ATTRIBUTES {
|
|
163
|
+
Sid: sid,
|
|
164
|
+
Attributes: SE_GROUP_INTEGRITY,
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
let r = SetTokenInformation(
|
|
168
|
+
tok,
|
|
169
|
+
TokenIntegrityLevel,
|
|
170
|
+
&tml as *const _ as *const c_void,
|
|
171
|
+
size_of::<TOKEN_MANDATORY_LABEL>() as u32 + GetLengthSid(sid),
|
|
172
|
+
);
|
|
173
|
+
// Free regardless of outcome.
|
|
174
|
+
FreeSid(sid);
|
|
175
|
+
r.context("SetTokenInformation(IntegrityLevel)")?;
|
|
176
|
+
Ok(())
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/// Rewrite the token's default DACL so objects the child *creates*
|
|
181
|
+
/// (process, thread, mutex, …) are accessible to SYSTEM and to the
|
|
182
|
+
/// logon session. The donor leaked the SYSTEM SID here; this version
|
|
183
|
+
/// uses [`LocalPsid`] so it's freed on return.
|
|
184
|
+
///
|
|
185
|
+
/// We deliberately keep the **logon SID** grant rather than switching
|
|
186
|
+
/// it to the discriminator group — switching would prevent siblings
|
|
187
|
+
/// inside the sandbox (e.g. a parallel build's worker processes) from
|
|
188
|
+
/// opening each other's pipes/mutexes, since they all hold the group
|
|
189
|
+
/// deny-only.
|
|
190
|
+
fn set_default_dacl(tok: HANDLE, base: HANDLE) -> Result<()> {
|
|
191
|
+
let system = LocalPsid::from_string("S-1-5-18")?;
|
|
192
|
+
|
|
193
|
+
// Find the logon-session SID in the base token's groups.
|
|
194
|
+
let groups_buf = get_token_info(base, TokenGroups)?;
|
|
195
|
+
let logon = unsafe {
|
|
196
|
+
let tg = &*(groups_buf.as_ptr() as *const TOKEN_GROUPS);
|
|
197
|
+
let arr = std::slice::from_raw_parts(
|
|
198
|
+
tg.Groups.as_ptr(),
|
|
199
|
+
tg.GroupCount as usize,
|
|
200
|
+
);
|
|
201
|
+
arr.iter()
|
|
202
|
+
.find(|g| g.Attributes & (SE_GROUP_LOGON_ID as u32) != 0)
|
|
203
|
+
.map(|g| g.Sid)
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
let sids: Vec<PSID> = match logon {
|
|
207
|
+
Some(l) => vec![system.as_psid(), l],
|
|
208
|
+
None => vec![system.as_psid()],
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// Sized ACL: header + per-ACE (8-byte fixed prefix + SID body).
|
|
212
|
+
const ACE_FIXED: usize = 8;
|
|
213
|
+
let mut total = size_of::<ACL>();
|
|
214
|
+
for s in &sids {
|
|
215
|
+
total += ACE_FIXED + unsafe { GetLengthSid(*s) } as usize;
|
|
216
|
+
}
|
|
217
|
+
total = (total + 3) & !3;
|
|
218
|
+
let mut buf = vec![0u8; total];
|
|
219
|
+
let acl = buf.as_mut_ptr() as *mut ACL;
|
|
220
|
+
unsafe {
|
|
221
|
+
InitializeAcl(acl, total as u32, ACL_REVISION)
|
|
222
|
+
.context("InitializeAcl(default DACL)")?;
|
|
223
|
+
// GENERIC_ALL.
|
|
224
|
+
const GENERIC_ALL: u32 = 0x1000_0000;
|
|
225
|
+
for s in &sids {
|
|
226
|
+
AddAccessAllowedAce(acl, ACL_REVISION, GENERIC_ALL, *s)
|
|
227
|
+
.context("AddAccessAllowedAce(default DACL)")?;
|
|
228
|
+
}
|
|
229
|
+
let tdd = TOKEN_DEFAULT_DACL { DefaultDacl: acl };
|
|
230
|
+
SetTokenInformation(
|
|
231
|
+
tok,
|
|
232
|
+
TokenDefaultDacl,
|
|
233
|
+
&tdd as *const _ as *const c_void,
|
|
234
|
+
size_of::<TOKEN_DEFAULT_DACL>() as u32,
|
|
235
|
+
)
|
|
236
|
+
.context("SetTokenInformation(DefaultDacl)")?;
|
|
237
|
+
}
|
|
238
|
+
// `groups_buf` (which backs `logon`) and `system` both drop here.
|
|
239
|
+
Ok(())
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
fn get_token_info(tok: HANDLE, cls: TOKEN_INFORMATION_CLASS) -> Result<Vec<u8>> {
|
|
243
|
+
unsafe {
|
|
244
|
+
let mut len = 0u32;
|
|
245
|
+
let _ = GetTokenInformation(tok, cls, None, 0, &mut len);
|
|
246
|
+
if len == 0 {
|
|
247
|
+
return Err(anyhow!(
|
|
248
|
+
"GetTokenInformation({cls:?}) sizing returned 0"
|
|
249
|
+
));
|
|
250
|
+
}
|
|
251
|
+
let mut buf = vec![0u8; len as usize];
|
|
252
|
+
GetTokenInformation(
|
|
253
|
+
tok,
|
|
254
|
+
cls,
|
|
255
|
+
Some(buf.as_mut_ptr() as *mut c_void),
|
|
256
|
+
len,
|
|
257
|
+
&mut len,
|
|
258
|
+
)
|
|
259
|
+
.with_context(|| format!("GetTokenInformation({cls:?})"))?;
|
|
260
|
+
Ok(buf)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/// Every privilege LUID in `base` except those named in `keep`.
|
|
265
|
+
fn privileges_except(
|
|
266
|
+
base: HANDLE,
|
|
267
|
+
keep: &[&str],
|
|
268
|
+
) -> Result<Vec<LUID_AND_ATTRIBUTES>> {
|
|
269
|
+
let keep_luids: Vec<LUID> = keep
|
|
270
|
+
.iter()
|
|
271
|
+
.filter_map(|n| {
|
|
272
|
+
let mut l = LUID::default();
|
|
273
|
+
let w = wstr(n);
|
|
274
|
+
unsafe { LookupPrivilegeValueW(None, pcwstr(&w), &mut l).ok()? };
|
|
275
|
+
Some(l)
|
|
276
|
+
})
|
|
277
|
+
.collect();
|
|
278
|
+
let buf = get_token_info(base, TokenPrivileges)?;
|
|
279
|
+
unsafe {
|
|
280
|
+
let tp = &*(buf.as_ptr() as *const TOKEN_PRIVILEGES);
|
|
281
|
+
let arr = std::slice::from_raw_parts(
|
|
282
|
+
tp.Privileges.as_ptr(),
|
|
283
|
+
tp.PrivilegeCount as usize,
|
|
284
|
+
);
|
|
285
|
+
Ok(arr
|
|
286
|
+
.iter()
|
|
287
|
+
.filter(|p| {
|
|
288
|
+
!keep_luids.iter().any(|k| {
|
|
289
|
+
k.LowPart == p.Luid.LowPart && k.HighPart == p.Luid.HighPart
|
|
290
|
+
})
|
|
291
|
+
})
|
|
292
|
+
.map(|p| LUID_AND_ATTRIBUTES {
|
|
293
|
+
Luid: p.Luid,
|
|
294
|
+
Attributes: Default::default(),
|
|
295
|
+
})
|
|
296
|
+
.collect())
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
#[cfg(test)]
|
|
301
|
+
mod tests {
|
|
302
|
+
use super::*;
|
|
303
|
+
use windows::Win32::Foundation::CloseHandle;
|
|
304
|
+
|
|
305
|
+
#[test]
|
|
306
|
+
fn restricted_token_builds() {
|
|
307
|
+
// Group SID is BUILTIN\Users — always present, so the test
|
|
308
|
+
// doesn't depend on the discriminator group being installed.
|
|
309
|
+
let base = open_self_token().expect("open_self_token");
|
|
310
|
+
let r = make_sandbox_token(base, "S-1-5-32-545");
|
|
311
|
+
unsafe {
|
|
312
|
+
let _ = CloseHandle(base);
|
|
313
|
+
}
|
|
314
|
+
let tok = r.expect("make_sandbox_token");
|
|
315
|
+
let prim = to_primary(tok).expect("to_primary");
|
|
316
|
+
unsafe {
|
|
317
|
+
let _ = CloseHandle(tok);
|
|
318
|
+
let _ = CloseHandle(prim);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
#[test]
|
|
323
|
+
fn privileges_except_keeps_change_notify() {
|
|
324
|
+
let base = open_self_token().expect("open_self_token");
|
|
325
|
+
let to_delete =
|
|
326
|
+
privileges_except(base, &["SeChangeNotifyPrivilege"]).unwrap();
|
|
327
|
+
// Resolve SeChangeNotifyPrivilege's LUID and assert it's NOT
|
|
328
|
+
// in the deletion set.
|
|
329
|
+
let mut keep = LUID::default();
|
|
330
|
+
let w = wstr("SeChangeNotifyPrivilege");
|
|
331
|
+
unsafe {
|
|
332
|
+
LookupPrivilegeValueW(None, pcwstr(&w), &mut keep).unwrap();
|
|
333
|
+
let _ = CloseHandle(base);
|
|
334
|
+
}
|
|
335
|
+
assert!(
|
|
336
|
+
!to_delete.iter().any(|p| p.Luid.LowPart == keep.LowPart
|
|
337
|
+
&& p.Luid.HighPart == keep.HighPart),
|
|
338
|
+
"SeChangeNotifyPrivilege must not be in the deletion set"
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
//! Small Win32 string helpers shared by `sid.rs` and `wfp.rs`.
|
|
2
|
+
|
|
3
|
+
use std::ffi::c_void;
|
|
4
|
+
use windows::core::{PCWSTR, PWSTR};
|
|
5
|
+
use windows::Win32::Foundation::{LocalFree, HLOCAL};
|
|
6
|
+
|
|
7
|
+
/// UTF-8 → NUL-terminated UTF-16 buffer. Keep the returned `Vec`
|
|
8
|
+
/// alive for as long as the resulting `PCWSTR` / `PWSTR` is in use.
|
|
9
|
+
pub fn wstr(s: &str) -> Vec<u16> {
|
|
10
|
+
s.encode_utf16().chain(std::iter::once(0)).collect()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/// Borrow a `Vec<u16>` from `wstr` as a `PCWSTR`.
|
|
14
|
+
pub fn pcwstr(buf: &[u16]) -> PCWSTR {
|
|
15
|
+
PCWSTR(buf.as_ptr())
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/// Read a NUL-terminated `PWSTR` (typically returned by a Win32 API
|
|
19
|
+
/// that allocates) into an owned `String`. Caller still owns the
|
|
20
|
+
/// underlying allocation.
|
|
21
|
+
pub fn from_pwstr(p: PWSTR) -> String {
|
|
22
|
+
if p.is_null() {
|
|
23
|
+
return String::new();
|
|
24
|
+
}
|
|
25
|
+
let mut len = 0usize;
|
|
26
|
+
unsafe {
|
|
27
|
+
while *p.0.add(len) != 0 {
|
|
28
|
+
len += 1;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
let slice = unsafe { std::slice::from_raw_parts(p.0, len) };
|
|
32
|
+
String::from_utf16_lossy(slice)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// `LocalFree` a pointer returned by a Win32 API documented to require
|
|
36
|
+
/// it (e.g. `ConvertSidToStringSidW`,
|
|
37
|
+
/// `ConvertStringSecurityDescriptorToSecurityDescriptorW`).
|
|
38
|
+
pub fn local_free(p: *mut c_void) {
|
|
39
|
+
unsafe {
|
|
40
|
+
let _ = LocalFree(Some(HLOCAL(p)));
|
|
41
|
+
}
|
|
42
|
+
}
|