mirror of
https://github.com/unslothai/unsloth.git
synced 2026-10-19 08:54:21 +00:00
Studio (Windows): read a runtime lock held by an elevated Unsloth process as busy, not a broken install (#13301)
* Studio (Windows): read a runtime lock held by an elevated Unsloth process as busy, not a broken install * Shorten test comments
This commit is contained in:
parent
9c4c4a1cee
commit
44d6349ba8
3 changed files with 142 additions and 5 deletions
|
|
@ -384,10 +384,19 @@ fn acquire_named_studio_runtime_launch_guard(
|
|||
const WAIT_OBJECT_0: u32 = 0x0000_0000;
|
||||
const WAIT_ABANDONED: u32 = 0x0000_0080;
|
||||
const WAIT_TIMEOUT: u32 = 0x0000_0102;
|
||||
// Only the rights the guard uses. CreateMutexW asks for MUTEX_ALL_ACCESS, which an
|
||||
// elevated holder's mutex denies to this process: error 5 instead of busy (#9928).
|
||||
const SYNCHRONIZE: u32 = 0x0010_0000;
|
||||
const MUTEX_MODIFY_STATE: u32 = 0x0000_0001;
|
||||
|
||||
let wide_name: Vec<u16> = name.encode_utf16().chain(std::iter::once(0)).collect();
|
||||
let handle = unsafe {
|
||||
windows_sys::Win32::System::Threading::CreateMutexW(std::ptr::null(), 0, wide_name.as_ptr())
|
||||
windows_sys::Win32::System::Threading::CreateMutexExW(
|
||||
std::ptr::null(),
|
||||
wide_name.as_ptr(),
|
||||
0,
|
||||
SYNCHRONIZE | MUTEX_MODIFY_STATE,
|
||||
)
|
||||
};
|
||||
if handle.is_null() {
|
||||
return Err(format!(
|
||||
|
|
@ -822,6 +831,76 @@ mod studio_runtime_launch_guard_tests {
|
|||
acquire_named_studio_runtime_launch_guard(&name).unwrap();
|
||||
}
|
||||
|
||||
// The access an elevated holder's mutex grants a non-elevated opener (#9928).
|
||||
#[test]
|
||||
fn a_holder_that_denies_full_access_reads_as_busy() {
|
||||
use windows_sys::Win32::Security::{
|
||||
AddAccessAllowedAce, CreateWellKnownSid, InitializeAcl, InitializeSecurityDescriptor,
|
||||
SetSecurityDescriptorDacl, WinWorldSid, ACL, ACL_REVISION, SECURITY_ATTRIBUTES,
|
||||
SECURITY_DESCRIPTOR, SECURITY_MAX_SID_SIZE,
|
||||
};
|
||||
|
||||
let name = format!(
|
||||
"Local\\UnslothStudioRuntimeGateAccessTest-{}-{}",
|
||||
std::process::id(),
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos()
|
||||
);
|
||||
let mut sid = [0_u8; SECURITY_MAX_SID_SIZE as usize];
|
||||
let mut sid_len = SECURITY_MAX_SID_SIZE;
|
||||
let mut acl = [0_u64; 16];
|
||||
let mut descriptor: SECURITY_DESCRIPTOR = unsafe { std::mem::zeroed() };
|
||||
let descriptor_ptr = (&mut descriptor as *mut SECURITY_DESCRIPTOR).cast();
|
||||
unsafe {
|
||||
assert_ne!(
|
||||
CreateWellKnownSid(
|
||||
WinWorldSid,
|
||||
std::ptr::null_mut(),
|
||||
sid.as_mut_ptr().cast(),
|
||||
&mut sid_len
|
||||
),
|
||||
0
|
||||
);
|
||||
let acl_ptr = acl.as_mut_ptr().cast::<ACL>();
|
||||
assert_ne!(
|
||||
InitializeAcl(acl_ptr, std::mem::size_of_val(&acl) as u32, ACL_REVISION),
|
||||
0
|
||||
);
|
||||
assert_ne!(
|
||||
AddAccessAllowedAce(acl_ptr, ACL_REVISION, 0x0010_0001, sid.as_mut_ptr().cast()),
|
||||
0
|
||||
);
|
||||
assert_ne!(InitializeSecurityDescriptor(descriptor_ptr, 1), 0);
|
||||
assert_ne!(SetSecurityDescriptorDacl(descriptor_ptr, 1, acl_ptr, 0), 0);
|
||||
}
|
||||
let attributes = SECURITY_ATTRIBUTES {
|
||||
nLength: std::mem::size_of::<SECURITY_ATTRIBUTES>() as u32,
|
||||
lpSecurityDescriptor: descriptor_ptr,
|
||||
bInheritHandle: 0,
|
||||
};
|
||||
let wide_name: Vec<u16> = name.encode_utf16().chain(std::iter::once(0)).collect();
|
||||
let holder = unsafe {
|
||||
windows_sys::Win32::System::Threading::CreateMutexW(&attributes, 1, wide_name.as_ptr())
|
||||
};
|
||||
assert!(!holder.is_null());
|
||||
|
||||
let contender_name = name.clone();
|
||||
let error = std::thread::spawn(move || {
|
||||
acquire_named_studio_runtime_launch_guard(&contender_name)
|
||||
.err()
|
||||
.expect("second launcher unexpectedly acquired the gate")
|
||||
})
|
||||
.join()
|
||||
.unwrap();
|
||||
unsafe {
|
||||
let _ = windows_sys::Win32::System::Threading::ReleaseMutex(holder);
|
||||
let _ = windows_sys::Win32::Foundation::CloseHandle(holder);
|
||||
}
|
||||
assert_eq!(error, STUDIO_RUNTIME_GATE_BUSY);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn guarded_operation_is_skipped_while_busy_and_runs_after_release() {
|
||||
let name = format!(
|
||||
|
|
|
|||
|
|
@ -441,6 +441,61 @@ def test_runtime_gate_blocks_another_thread_and_recovers():
|
|||
assert acquired is True
|
||||
|
||||
|
||||
class _SecurityAttributes(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("length", wintypes.DWORD),
|
||||
("descriptor", ctypes.c_void_p),
|
||||
("inherit", wintypes.BOOL),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name != "nt", reason = "Windows named mutexes are required")
|
||||
def test_runtime_gate_reads_a_holder_it_cannot_fully_open_as_busy(tmp_path):
|
||||
# The access an elevated holder's mutex grants a non-elevated opener (#9928).
|
||||
kernel32 = ctypes.WinDLL("kernel32", use_last_error = True)
|
||||
advapi32 = ctypes.WinDLL("advapi32", use_last_error = True)
|
||||
kernel32.CreateMutexW.argtypes = [ctypes.c_void_p, wintypes.BOOL, wintypes.LPCWSTR]
|
||||
kernel32.CreateMutexW.restype = wintypes.HANDLE
|
||||
kernel32.ReleaseMutex.argtypes = [wintypes.HANDLE]
|
||||
kernel32.CloseHandle.argtypes = [wintypes.HANDLE]
|
||||
kernel32.LocalFree.argtypes = [ctypes.c_void_p]
|
||||
kernel32.LocalFree.restype = ctypes.c_void_p
|
||||
convert = advapi32.ConvertStringSecurityDescriptorToSecurityDescriptorW
|
||||
convert.argtypes = [
|
||||
wintypes.LPCWSTR,
|
||||
wintypes.DWORD,
|
||||
ctypes.POINTER(ctypes.c_void_p),
|
||||
ctypes.c_void_p,
|
||||
]
|
||||
convert.restype = wintypes.BOOL
|
||||
descriptor = ctypes.c_void_p()
|
||||
assert convert("D:(A;;0x00100001;;;WD)", 1, ctypes.byref(descriptor), None)
|
||||
attributes = _SecurityAttributes(ctypes.sizeof(_SecurityAttributes), descriptor, False)
|
||||
name = gate.runtime_mutex_name_for_studio_home(tmp_path)
|
||||
holder = kernel32.CreateMutexW(ctypes.byref(attributes), True, name)
|
||||
assert holder
|
||||
observed: list[str] = []
|
||||
|
||||
def contend() -> None:
|
||||
try:
|
||||
with gate.studio_runtime_launch_guard(tmp_path):
|
||||
observed.append("acquired")
|
||||
except gate.StudioRuntimeGateBusy:
|
||||
observed.append("blocked")
|
||||
except OSError as exc:
|
||||
observed.append(f"winerror {exc.winerror}")
|
||||
|
||||
try:
|
||||
contender = threading.Thread(target = contend)
|
||||
contender.start()
|
||||
contender.join(timeout = 10)
|
||||
assert observed == ["blocked"]
|
||||
finally:
|
||||
assert kernel32.ReleaseMutex(holder)
|
||||
assert kernel32.CloseHandle(holder)
|
||||
kernel32.LocalFree(descriptor)
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name != "nt", reason = "Windows named mutexes are required")
|
||||
def test_custom_root_runtime_gate_blocks_another_thread(tmp_path):
|
||||
kernel32 = ctypes.WinDLL("kernel32", use_last_error = True)
|
||||
|
|
|
|||
|
|
@ -233,12 +233,13 @@ def studio_runtime_launch_guard(
|
|||
return
|
||||
|
||||
kernel32 = ctypes.WinDLL("kernel32", use_last_error = True)
|
||||
kernel32.CreateMutexW.argtypes = [
|
||||
kernel32.CreateMutexExW.argtypes = [
|
||||
ctypes.c_void_p,
|
||||
wintypes.BOOL,
|
||||
wintypes.LPCWSTR,
|
||||
wintypes.DWORD,
|
||||
wintypes.DWORD,
|
||||
]
|
||||
kernel32.CreateMutexW.restype = wintypes.HANDLE
|
||||
kernel32.CreateMutexExW.restype = wintypes.HANDLE
|
||||
kernel32.WaitForSingleObject.argtypes = [wintypes.HANDLE, wintypes.DWORD]
|
||||
kernel32.WaitForSingleObject.restype = wintypes.DWORD
|
||||
kernel32.ReleaseMutex.argtypes = [wintypes.HANDLE]
|
||||
|
|
@ -247,7 +248,9 @@ def studio_runtime_launch_guard(
|
|||
kernel32.CloseHandle.restype = wintypes.BOOL
|
||||
|
||||
name = runtime_mutex_name_for_studio_home(studio_home)
|
||||
handle = kernel32.CreateMutexW(None, False, name)
|
||||
# SYNCHRONIZE | MUTEX_MODIFY_STATE, not CreateMutexW's MUTEX_ALL_ACCESS, which an elevated
|
||||
# holder's mutex denies to a non-elevated opener: error 5 instead of busy (#9928).
|
||||
handle = kernel32.CreateMutexExW(None, name, 0, 0x00100001)
|
||||
if not handle:
|
||||
raise ctypes.WinError(ctypes.get_last_error())
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue