Skip to content

Commit 79ce79a

Browse files
use a SandboxUsers group for ACLs instead of granting to each sandbox user separately (#8483)
This is more future-proof if we ever decide to add additional Sandbox Users for new functionality This also moves some more user-related code into a new file for code cleanliness
1 parent 66b7c67 commit 79ce79a

File tree

2 files changed

+356
-336
lines changed

2 files changed

+356
-336
lines changed
Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
#![cfg(target_os = "windows")]
2+
3+
use anyhow::Result;
4+
use base64::engine::general_purpose::STANDARD as BASE64;
5+
use base64::Engine;
6+
use rand::rngs::SmallRng;
7+
use rand::RngCore;
8+
use rand::SeedableRng;
9+
use serde::Serialize;
10+
use std::ffi::c_void;
11+
use std::ffi::OsStr;
12+
use std::fs::File;
13+
use std::path::Path;
14+
use std::path::PathBuf;
15+
use windows_sys::Win32::Foundation::GetLastError;
16+
use windows_sys::Win32::Foundation::ERROR_INSUFFICIENT_BUFFER;
17+
use windows_sys::Win32::NetworkManagement::NetManagement::NERR_Success;
18+
use windows_sys::Win32::NetworkManagement::NetManagement::NetLocalGroupAdd;
19+
use windows_sys::Win32::NetworkManagement::NetManagement::NetLocalGroupAddMembers;
20+
use windows_sys::Win32::NetworkManagement::NetManagement::NetUserAdd;
21+
use windows_sys::Win32::NetworkManagement::NetManagement::NetUserSetInfo;
22+
use windows_sys::Win32::NetworkManagement::NetManagement::LOCALGROUP_INFO_1;
23+
use windows_sys::Win32::NetworkManagement::NetManagement::LOCALGROUP_MEMBERS_INFO_3;
24+
use windows_sys::Win32::NetworkManagement::NetManagement::UF_DONT_EXPIRE_PASSWD;
25+
use windows_sys::Win32::NetworkManagement::NetManagement::UF_SCRIPT;
26+
use windows_sys::Win32::NetworkManagement::NetManagement::USER_INFO_1;
27+
use windows_sys::Win32::NetworkManagement::NetManagement::USER_INFO_1003;
28+
use windows_sys::Win32::NetworkManagement::NetManagement::USER_PRIV_USER;
29+
use windows_sys::Win32::Security::Authorization::ConvertStringSidToSidW;
30+
use windows_sys::Win32::Security::LookupAccountNameW;
31+
use windows_sys::Win32::Security::SID_NAME_USE;
32+
33+
use codex_windows_sandbox::dpapi_protect;
34+
use codex_windows_sandbox::sandbox_dir;
35+
use codex_windows_sandbox::string_from_sid_bytes;
36+
use codex_windows_sandbox::to_wide;
37+
use codex_windows_sandbox::SETUP_VERSION;
38+
39+
pub const SANDBOX_USERS_GROUP: &str = "CodexSandboxUsers";
40+
const SANDBOX_USERS_GROUP_COMMENT: &str = "Codex sandbox internal group (managed)";
41+
42+
pub fn ensure_sandbox_users_group(log: &mut File) -> Result<()> {
43+
ensure_local_group(SANDBOX_USERS_GROUP, SANDBOX_USERS_GROUP_COMMENT, log)
44+
}
45+
46+
pub fn resolve_sandbox_users_group_sid() -> Result<Vec<u8>> {
47+
resolve_sid(SANDBOX_USERS_GROUP)
48+
}
49+
50+
pub fn provision_sandbox_users(
51+
codex_home: &Path,
52+
offline_username: &str,
53+
online_username: &str,
54+
log: &mut File,
55+
) -> Result<()> {
56+
ensure_sandbox_users_group(log)?;
57+
super::log_line(
58+
log,
59+
&format!("ensuring sandbox users offline={offline_username} online={online_username}"),
60+
)?;
61+
let offline_password = random_password();
62+
let online_password = random_password();
63+
ensure_sandbox_user(offline_username, &offline_password, log)?;
64+
ensure_sandbox_user(online_username, &online_password, log)?;
65+
write_secrets(
66+
codex_home,
67+
offline_username,
68+
&offline_password,
69+
online_username,
70+
&online_password,
71+
)?;
72+
Ok(())
73+
}
74+
75+
pub fn ensure_sandbox_user(username: &str, password: &str, log: &mut File) -> Result<()> {
76+
ensure_local_user(username, password, log)?;
77+
ensure_local_group_member(SANDBOX_USERS_GROUP, username)?;
78+
Ok(())
79+
}
80+
81+
pub fn ensure_local_user(name: &str, password: &str, log: &mut File) -> Result<()> {
82+
let name_w = to_wide(OsStr::new(name));
83+
let pwd_w = to_wide(OsStr::new(password));
84+
unsafe {
85+
let info = USER_INFO_1 {
86+
usri1_name: name_w.as_ptr() as *mut u16,
87+
usri1_password: pwd_w.as_ptr() as *mut u16,
88+
usri1_password_age: 0,
89+
usri1_priv: USER_PRIV_USER,
90+
usri1_home_dir: std::ptr::null_mut(),
91+
usri1_comment: std::ptr::null_mut(),
92+
usri1_flags: UF_SCRIPT | UF_DONT_EXPIRE_PASSWD,
93+
usri1_script_path: std::ptr::null_mut(),
94+
};
95+
let status = NetUserAdd(
96+
std::ptr::null(),
97+
1,
98+
&info as *const _ as *mut u8,
99+
std::ptr::null_mut(),
100+
);
101+
if status != NERR_Success {
102+
// Try update password via level 1003.
103+
let pw_info = USER_INFO_1003 {
104+
usri1003_password: pwd_w.as_ptr() as *mut u16,
105+
};
106+
let upd = NetUserSetInfo(
107+
std::ptr::null(),
108+
name_w.as_ptr(),
109+
1003,
110+
&pw_info as *const _ as *mut u8,
111+
std::ptr::null_mut(),
112+
);
113+
if upd != NERR_Success {
114+
super::log_line(log, &format!("NetUserSetInfo failed for {name} code {upd}"))?;
115+
return Err(anyhow::anyhow!(
116+
"failed to create/update user {name}, code {status}/{upd}"
117+
));
118+
}
119+
}
120+
121+
// Ensure the principal is a regular local user account.
122+
let group = to_wide(OsStr::new("Users"));
123+
let member = LOCALGROUP_MEMBERS_INFO_3 {
124+
lgrmi3_domainandname: name_w.as_ptr() as *mut u16,
125+
};
126+
let _ = NetLocalGroupAddMembers(
127+
std::ptr::null(),
128+
group.as_ptr(),
129+
3,
130+
&member as *const _ as *mut u8,
131+
1,
132+
);
133+
}
134+
Ok(())
135+
}
136+
137+
pub fn ensure_local_group(name: &str, comment: &str, log: &mut File) -> Result<()> {
138+
const ERROR_ALIAS_EXISTS: u32 = 1379;
139+
const NERR_GROUP_EXISTS: u32 = 2223;
140+
141+
let name_w = to_wide(OsStr::new(name));
142+
let comment_w = to_wide(OsStr::new(comment));
143+
unsafe {
144+
let info = LOCALGROUP_INFO_1 {
145+
lgrpi1_name: name_w.as_ptr() as *mut u16,
146+
lgrpi1_comment: comment_w.as_ptr() as *mut u16,
147+
};
148+
let mut parm_err: u32 = 0;
149+
let status = NetLocalGroupAdd(
150+
std::ptr::null(),
151+
1,
152+
&info as *const _ as *mut u8,
153+
&mut parm_err as *mut _,
154+
);
155+
if status != NERR_Success && status != ERROR_ALIAS_EXISTS && status != NERR_GROUP_EXISTS {
156+
super::log_line(
157+
log,
158+
&format!("NetLocalGroupAdd failed for {name} code {status} parm_err={parm_err}"),
159+
)?;
160+
anyhow::bail!("failed to create local group {name}, code {status}");
161+
}
162+
}
163+
Ok(())
164+
}
165+
166+
pub fn ensure_local_group_member(group_name: &str, member_name: &str) -> Result<()> {
167+
// If the member is already in the group, NetLocalGroupAddMembers may
168+
// return an error code. We don't care.
169+
let group_w = to_wide(OsStr::new(group_name));
170+
let member_w = to_wide(OsStr::new(member_name));
171+
unsafe {
172+
let member = LOCALGROUP_MEMBERS_INFO_3 {
173+
lgrmi3_domainandname: member_w.as_ptr() as *mut u16,
174+
};
175+
let _ = NetLocalGroupAddMembers(
176+
std::ptr::null(),
177+
group_w.as_ptr(),
178+
3,
179+
&member as *const _ as *mut u8,
180+
1,
181+
);
182+
}
183+
Ok(())
184+
}
185+
186+
pub fn resolve_sid(name: &str) -> Result<Vec<u8>> {
187+
let name_w = to_wide(OsStr::new(name));
188+
let mut sid_buffer = vec![0u8; 68];
189+
let mut sid_len: u32 = sid_buffer.len() as u32;
190+
let mut domain: Vec<u16> = Vec::new();
191+
let mut domain_len: u32 = 0;
192+
let mut use_type: SID_NAME_USE = 0;
193+
loop {
194+
let ok = unsafe {
195+
LookupAccountNameW(
196+
std::ptr::null(),
197+
name_w.as_ptr(),
198+
sid_buffer.as_mut_ptr() as *mut c_void,
199+
&mut sid_len,
200+
domain.as_mut_ptr(),
201+
&mut domain_len,
202+
&mut use_type,
203+
)
204+
};
205+
if ok != 0 {
206+
sid_buffer.truncate(sid_len as usize);
207+
return Ok(sid_buffer);
208+
}
209+
let err = unsafe { GetLastError() };
210+
if err == ERROR_INSUFFICIENT_BUFFER {
211+
sid_buffer.resize(sid_len as usize, 0);
212+
domain.resize(domain_len as usize, 0);
213+
continue;
214+
}
215+
return Err(anyhow::anyhow!(
216+
"LookupAccountNameW failed for {name}: {err}"
217+
));
218+
}
219+
}
220+
221+
pub fn sid_bytes_to_psid(sid: &[u8]) -> Result<*mut c_void> {
222+
let sid_str = string_from_sid_bytes(sid).map_err(anyhow::Error::msg)?;
223+
let sid_w = to_wide(OsStr::new(&sid_str));
224+
let mut psid: *mut c_void = std::ptr::null_mut();
225+
if unsafe { ConvertStringSidToSidW(sid_w.as_ptr(), &mut psid) } == 0 {
226+
return Err(anyhow::anyhow!(
227+
"ConvertStringSidToSidW failed: {}",
228+
unsafe { GetLastError() }
229+
));
230+
}
231+
Ok(psid)
232+
}
233+
234+
fn random_password() -> String {
235+
const CHARS: &[u8] =
236+
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+";
237+
let mut rng = SmallRng::from_entropy();
238+
let mut buf = [0u8; 24];
239+
rng.fill_bytes(&mut buf);
240+
buf.iter()
241+
.map(|b| {
242+
let idx = (*b as usize) % CHARS.len();
243+
CHARS[idx] as char
244+
})
245+
.collect()
246+
}
247+
248+
#[derive(Serialize)]
249+
struct SandboxUserRecord {
250+
username: String,
251+
password: String,
252+
}
253+
254+
#[derive(Serialize)]
255+
struct SandboxUsersFile {
256+
version: u32,
257+
offline: SandboxUserRecord,
258+
online: SandboxUserRecord,
259+
}
260+
261+
#[derive(Serialize)]
262+
struct SetupMarker {
263+
version: u32,
264+
offline_username: String,
265+
online_username: String,
266+
created_at: String,
267+
read_roots: Vec<PathBuf>,
268+
write_roots: Vec<PathBuf>,
269+
}
270+
271+
fn write_secrets(
272+
codex_home: &Path,
273+
offline_user: &str,
274+
offline_pwd: &str,
275+
online_user: &str,
276+
online_pwd: &str,
277+
) -> Result<()> {
278+
let sandbox_dir = sandbox_dir(codex_home);
279+
std::fs::create_dir_all(&sandbox_dir)?;
280+
let offline_blob = dpapi_protect(offline_pwd.as_bytes())?;
281+
let online_blob = dpapi_protect(online_pwd.as_bytes())?;
282+
let users = SandboxUsersFile {
283+
version: SETUP_VERSION,
284+
offline: SandboxUserRecord {
285+
username: offline_user.to_string(),
286+
password: BASE64.encode(offline_blob),
287+
},
288+
online: SandboxUserRecord {
289+
username: online_user.to_string(),
290+
password: BASE64.encode(online_blob),
291+
},
292+
};
293+
let marker = SetupMarker {
294+
version: SETUP_VERSION,
295+
offline_username: offline_user.to_string(),
296+
online_username: online_user.to_string(),
297+
created_at: chrono::Utc::now().to_rfc3339(),
298+
read_roots: Vec::new(),
299+
write_roots: Vec::new(),
300+
};
301+
let users_path = sandbox_dir.join("sandbox_users.json");
302+
let marker_path = sandbox_dir.join("setup_marker.json");
303+
std::fs::write(users_path, serde_json::to_vec_pretty(&users)?)?;
304+
std::fs::write(marker_path, serde_json::to_vec_pretty(&marker)?)?;
305+
Ok(())
306+
}

0 commit comments

Comments
 (0)