mirror of
https://github.com/sliverarmory/reflektor
synced 2026-08-25 09:14:48 +00:00
497 lines
17 KiB
Rust
497 lines
17 KiB
Rust
#![no_std]
|
|
|
|
#[cfg(all(target_os = "macos", not(target_pointer_width = "64")))]
|
|
compile_error!("the macOS fixture supports only 64-bit Apple targets");
|
|
|
|
use core::panic::PanicInfo;
|
|
|
|
const MARKER_OK: &[u8] = b"ok:200";
|
|
|
|
#[panic_handler]
|
|
fn panic(_info: &PanicInfo<'_>) -> ! {
|
|
loop {
|
|
core::hint::spin_loop();
|
|
}
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub extern "C" fn StartW() {
|
|
#[cfg(target_os = "macos")]
|
|
macos_thread::run();
|
|
|
|
#[cfg(not(target_os = "macos"))]
|
|
request_and_record();
|
|
}
|
|
|
|
fn request_and_record() {
|
|
let result = platform::get_example_com();
|
|
platform::write_marker(match result {
|
|
Ok(()) => MARKER_OK,
|
|
Err(stage) => stage,
|
|
});
|
|
}
|
|
|
|
#[cfg(target_os = "macos")]
|
|
mod macos_thread {
|
|
use core::ffi::{c_int, c_long, c_void};
|
|
use core::mem::MaybeUninit;
|
|
use core::ptr;
|
|
|
|
const REQUEST_STACK_SIZE: usize = 8 * 1024 * 1024;
|
|
|
|
// Darwin's 64-bit pthread_attr_t is an opaque long followed by 56 bytes.
|
|
// Both supported Apple targets are 64-bit.
|
|
#[repr(C)]
|
|
struct PthreadAttr {
|
|
signature: c_long,
|
|
opaque: [u8; 56],
|
|
}
|
|
|
|
type Pthread = *mut c_void;
|
|
|
|
const _: [(); 64] = [(); core::mem::size_of::<PthreadAttr>()];
|
|
const _: [(); 8] = [(); core::mem::align_of::<PthreadAttr>()];
|
|
|
|
#[link(name = "c")]
|
|
unsafe extern "C" {
|
|
fn pthread_attr_init(attr: *mut PthreadAttr) -> c_int;
|
|
fn pthread_attr_setstacksize(attr: *mut PthreadAttr, stack_size: usize) -> c_int;
|
|
fn pthread_attr_destroy(attr: *mut PthreadAttr) -> c_int;
|
|
fn pthread_create(
|
|
thread: *mut Pthread,
|
|
attr: *const PthreadAttr,
|
|
start: unsafe extern "C" fn(*mut c_void) -> *mut c_void,
|
|
argument: *mut c_void,
|
|
) -> c_int;
|
|
fn pthread_join(thread: Pthread, result: *mut *mut c_void) -> c_int;
|
|
}
|
|
|
|
unsafe extern "C" fn request_worker(_argument: *mut c_void) -> *mut c_void {
|
|
super::request_and_record();
|
|
ptr::null_mut()
|
|
}
|
|
|
|
pub fn run() {
|
|
// Go-created secondary threads have a comparatively small native stack
|
|
// on Intel macOS. libcurl may exhaust it, so execute the request on a
|
|
// joined pthread with the same stack size as a macOS main thread.
|
|
unsafe {
|
|
let mut attr = MaybeUninit::<PthreadAttr>::uninit();
|
|
if pthread_attr_init(attr.as_mut_ptr()) != 0 {
|
|
super::platform::write_marker(b"error:pthread-attr-init");
|
|
return;
|
|
}
|
|
|
|
let attr = attr.as_mut_ptr();
|
|
if pthread_attr_setstacksize(attr, REQUEST_STACK_SIZE) != 0 {
|
|
let _ = pthread_attr_destroy(attr);
|
|
super::platform::write_marker(b"error:pthread-stack-size");
|
|
return;
|
|
}
|
|
|
|
let mut thread = MaybeUninit::<Pthread>::uninit();
|
|
let create_result =
|
|
pthread_create(thread.as_mut_ptr(), attr, request_worker, ptr::null_mut());
|
|
let _ = pthread_attr_destroy(attr);
|
|
if create_result != 0 {
|
|
super::platform::write_marker(b"error:pthread-create");
|
|
return;
|
|
}
|
|
|
|
if pthread_join(thread.assume_init(), ptr::null_mut()) != 0 {
|
|
super::platform::write_marker(b"error:pthread-join");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
|
mod platform {
|
|
use core::ffi::{c_char, c_int, c_long, c_uint, c_void};
|
|
use core::ptr;
|
|
|
|
const CURL_GLOBAL_DEFAULT: c_long = 3;
|
|
const CURLE_OK: c_int = 0;
|
|
const CURLOPT_WRITEDATA: c_uint = 10_001;
|
|
const CURLOPT_URL: c_uint = 10_002;
|
|
const CURLOPT_USERAGENT: c_uint = 10_018;
|
|
const CURLOPT_WRITEFUNCTION: c_uint = 20_011;
|
|
const CURLOPT_TIMEOUT: c_uint = 13;
|
|
const CURLOPT_FOLLOWLOCATION: c_uint = 52;
|
|
const CURLOPT_CONNECTTIMEOUT: c_uint = 78;
|
|
const CURLOPT_NOSIGNAL: c_uint = 99;
|
|
const CURLINFO_RESPONSE_CODE: c_uint = 0x20_0002;
|
|
|
|
static URL: &[u8] = b"https://example.com/\0";
|
|
static USER_AGENT: &[u8] = b"reflektor-rust-fixture/1.0\0";
|
|
static MARKER_ENV: &[u8] = b"REFLEKTOR_MARKER\0";
|
|
static DEFAULT_MARKER: &[u8] = b"/tmp/reflektor_rust_marker.txt\0";
|
|
static WRITE_MODE: &[u8] = b"wb\0";
|
|
|
|
#[link(name = "curl")]
|
|
unsafe extern "C" {
|
|
fn curl_global_init(flags: c_long) -> c_int;
|
|
fn curl_easy_init() -> *mut c_void;
|
|
fn curl_easy_setopt(handle: *mut c_void, option: c_uint, ...) -> c_int;
|
|
fn curl_easy_perform(handle: *mut c_void) -> c_int;
|
|
fn curl_easy_getinfo(handle: *mut c_void, info: c_uint, ...) -> c_int;
|
|
fn curl_easy_cleanup(handle: *mut c_void);
|
|
}
|
|
|
|
#[link(name = "c")]
|
|
unsafe extern "C" {
|
|
fn getenv(name: *const c_char) -> *mut c_char;
|
|
fn fopen(path: *const c_char, mode: *const c_char) -> *mut c_void;
|
|
fn fwrite(ptr: *const c_void, size: usize, count: usize, stream: *mut c_void) -> usize;
|
|
fn fclose(stream: *mut c_void) -> c_int;
|
|
}
|
|
|
|
unsafe extern "C" fn count_body(
|
|
_data: *mut u8,
|
|
size: usize,
|
|
count: usize,
|
|
user_data: *mut c_void,
|
|
) -> usize {
|
|
let Some(total) = size.checked_mul(count) else {
|
|
return 0;
|
|
};
|
|
if total != 0 && !user_data.is_null() {
|
|
let counter = user_data.cast::<usize>();
|
|
// SAFETY: libcurl receives the address of body_bytes below and only
|
|
// invokes this callback before curl_easy_perform returns.
|
|
unsafe {
|
|
*counter = (*counter).saturating_add(total);
|
|
}
|
|
}
|
|
total
|
|
}
|
|
|
|
pub fn get_example_com() -> Result<(), &'static [u8]> {
|
|
// SAFETY: every pointer passed to libcurl remains live until the easy
|
|
// handle is cleaned up, and all option values match libcurl's C ABI.
|
|
unsafe {
|
|
if curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK {
|
|
return Err(b"error:curl-global-init");
|
|
}
|
|
|
|
let handle = curl_easy_init();
|
|
if handle.is_null() {
|
|
return Err(b"error:curl-easy-init");
|
|
}
|
|
|
|
let mut body_bytes = 0usize;
|
|
let callback = count_body as *const () as *const c_void;
|
|
let options_ok = curl_easy_setopt(handle, CURLOPT_URL, URL.as_ptr()) == CURLE_OK
|
|
&& curl_easy_setopt(handle, CURLOPT_USERAGENT, USER_AGENT.as_ptr()) == CURLE_OK
|
|
&& curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1 as c_long) == CURLE_OK
|
|
&& curl_easy_setopt(handle, CURLOPT_CONNECTTIMEOUT, 10 as c_long) == CURLE_OK
|
|
&& curl_easy_setopt(handle, CURLOPT_TIMEOUT, 20 as c_long) == CURLE_OK
|
|
&& curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1 as c_long) == CURLE_OK
|
|
&& curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, callback) == CURLE_OK
|
|
&& curl_easy_setopt(
|
|
handle,
|
|
CURLOPT_WRITEDATA,
|
|
ptr::addr_of_mut!(body_bytes).cast::<c_void>(),
|
|
) == CURLE_OK;
|
|
if !options_ok {
|
|
curl_easy_cleanup(handle);
|
|
return Err(b"error:curl-setopt");
|
|
}
|
|
|
|
if curl_easy_perform(handle) != CURLE_OK {
|
|
curl_easy_cleanup(handle);
|
|
return Err(b"error:curl-perform");
|
|
}
|
|
|
|
let mut status = 0 as c_long;
|
|
let info_result =
|
|
curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, ptr::addr_of_mut!(status));
|
|
curl_easy_cleanup(handle);
|
|
|
|
if info_result != CURLE_OK {
|
|
return Err(b"error:curl-getinfo");
|
|
}
|
|
if status != 200 {
|
|
return Err(b"error:http-status");
|
|
}
|
|
if body_bytes == 0 {
|
|
return Err(b"error:empty-body");
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn write_marker(payload: &[u8]) {
|
|
// SAFETY: the environment/default paths and mode are NUL-terminated,
|
|
// and payload remains valid for the duration of fwrite.
|
|
unsafe {
|
|
let configured = getenv(MARKER_ENV.as_ptr().cast::<c_char>());
|
|
let path = if configured.is_null() || *configured == 0 {
|
|
DEFAULT_MARKER.as_ptr().cast::<c_char>()
|
|
} else {
|
|
configured.cast_const()
|
|
};
|
|
let stream = fopen(path, WRITE_MODE.as_ptr().cast::<c_char>());
|
|
if stream.is_null() {
|
|
return;
|
|
}
|
|
let _ = fwrite(payload.as_ptr().cast::<c_void>(), 1, payload.len(), stream);
|
|
let _ = fclose(stream);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
mod platform {
|
|
use core::ffi::{c_char, c_void};
|
|
use core::mem::{self, MaybeUninit};
|
|
use core::ptr;
|
|
|
|
type Bool = i32;
|
|
type Dword = u32;
|
|
type Hinternet = *mut c_void;
|
|
type Handle = *mut c_void;
|
|
|
|
const WINHTTP_ACCESS_TYPE_DEFAULT_PROXY: Dword = 0;
|
|
const INTERNET_DEFAULT_HTTPS_PORT: u16 = 443;
|
|
const WINHTTP_FLAG_SECURE: Dword = 0x0080_0000;
|
|
const WINHTTP_QUERY_STATUS_CODE: Dword = 19;
|
|
const WINHTTP_QUERY_FLAG_NUMBER: Dword = 0x2000_0000;
|
|
|
|
const GENERIC_WRITE: Dword = 0x4000_0000;
|
|
const FILE_SHARE_READ: Dword = 0x0000_0001;
|
|
const FILE_SHARE_WRITE: Dword = 0x0000_0002;
|
|
const CREATE_ALWAYS: Dword = 2;
|
|
const FILE_ATTRIBUTE_NORMAL: Dword = 0x0000_0080;
|
|
const INVALID_HANDLE_VALUE: Handle = usize::MAX as Handle;
|
|
|
|
const fn wide<const N: usize>(bytes: &[u8; N]) -> [u16; N] {
|
|
let mut result = [0u16; N];
|
|
let mut index = 0;
|
|
while index < N {
|
|
result[index] = bytes[index] as u16;
|
|
index += 1;
|
|
}
|
|
result
|
|
}
|
|
|
|
static USER_AGENT: [u16; 27] = wide(b"reflektor-rust-fixture/1.0\0");
|
|
static HOST: [u16; 12] = wide(b"example.com\0");
|
|
static VERB: [u16; 4] = wide(b"GET\0");
|
|
static PATH: [u16; 2] = wide(b"/\0");
|
|
static MARKER_ENV: &[u8] = b"REFLEKTOR_MARKER\0";
|
|
static DEFAULT_MARKER: &[u8] = b"C:\\Windows\\Temp\\reflektor_rust_marker.txt\0";
|
|
const MARKER_PATH_CAPACITY: usize = 4096;
|
|
|
|
#[link(name = "winhttp")]
|
|
unsafe extern "system" {
|
|
fn WinHttpOpen(
|
|
user_agent: *const u16,
|
|
access_type: Dword,
|
|
proxy_name: *const u16,
|
|
proxy_bypass: *const u16,
|
|
flags: Dword,
|
|
) -> Hinternet;
|
|
fn WinHttpSetTimeouts(
|
|
session: Hinternet,
|
|
resolve_timeout: i32,
|
|
connect_timeout: i32,
|
|
send_timeout: i32,
|
|
receive_timeout: i32,
|
|
) -> Bool;
|
|
fn WinHttpConnect(
|
|
session: Hinternet,
|
|
server_name: *const u16,
|
|
server_port: u16,
|
|
reserved: Dword,
|
|
) -> Hinternet;
|
|
fn WinHttpOpenRequest(
|
|
connect: Hinternet,
|
|
verb: *const u16,
|
|
object_name: *const u16,
|
|
version: *const u16,
|
|
referrer: *const u16,
|
|
accept_types: *const *const u16,
|
|
flags: Dword,
|
|
) -> Hinternet;
|
|
fn WinHttpSendRequest(
|
|
request: Hinternet,
|
|
headers: *const u16,
|
|
headers_length: Dword,
|
|
optional: *mut c_void,
|
|
optional_length: Dword,
|
|
total_length: Dword,
|
|
context: usize,
|
|
) -> Bool;
|
|
fn WinHttpReceiveResponse(request: Hinternet, reserved: *mut c_void) -> Bool;
|
|
fn WinHttpQueryHeaders(
|
|
request: Hinternet,
|
|
info_level: Dword,
|
|
name: *const u16,
|
|
buffer: *mut c_void,
|
|
buffer_length: *mut Dword,
|
|
index: *mut Dword,
|
|
) -> Bool;
|
|
fn WinHttpReadData(
|
|
request: Hinternet,
|
|
buffer: *mut c_void,
|
|
bytes_to_read: Dword,
|
|
bytes_read: *mut Dword,
|
|
) -> Bool;
|
|
fn WinHttpCloseHandle(handle: Hinternet) -> Bool;
|
|
}
|
|
|
|
#[link(name = "kernel32")]
|
|
unsafe extern "system" {
|
|
fn GetEnvironmentVariableA(name: *const c_char, buffer: *mut c_char, size: Dword) -> Dword;
|
|
fn CreateFileA(
|
|
path: *const c_char,
|
|
desired_access: Dword,
|
|
share_mode: Dword,
|
|
security_attributes: *mut c_void,
|
|
creation_disposition: Dword,
|
|
flags_and_attributes: Dword,
|
|
template_file: Handle,
|
|
) -> Handle;
|
|
fn WriteFile(
|
|
file: Handle,
|
|
buffer: *const c_void,
|
|
bytes_to_write: Dword,
|
|
bytes_written: *mut Dword,
|
|
overlapped: *mut c_void,
|
|
) -> Bool;
|
|
fn CloseHandle(handle: Handle) -> Bool;
|
|
}
|
|
|
|
pub fn get_example_com() -> Result<(), &'static [u8]> {
|
|
// SAFETY: all WinHTTP handles are checked before use and closed on every
|
|
// exit path; string pointers refer to static NUL-terminated UTF-16 data.
|
|
unsafe {
|
|
let session = WinHttpOpen(
|
|
USER_AGENT.as_ptr(),
|
|
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
|
|
ptr::null(),
|
|
ptr::null(),
|
|
0,
|
|
);
|
|
if session.is_null() {
|
|
return Err(b"error:winhttp-open");
|
|
}
|
|
if WinHttpSetTimeouts(session, 10_000, 10_000, 10_000, 20_000) == 0 {
|
|
let _ = WinHttpCloseHandle(session);
|
|
return Err(b"error:winhttp-timeouts");
|
|
}
|
|
|
|
let connect = WinHttpConnect(session, HOST.as_ptr(), INTERNET_DEFAULT_HTTPS_PORT, 0);
|
|
if connect.is_null() {
|
|
let _ = WinHttpCloseHandle(session);
|
|
return Err(b"error:winhttp-connect");
|
|
}
|
|
|
|
let request = WinHttpOpenRequest(
|
|
connect,
|
|
VERB.as_ptr(),
|
|
PATH.as_ptr(),
|
|
ptr::null(),
|
|
ptr::null(),
|
|
ptr::null(),
|
|
WINHTTP_FLAG_SECURE,
|
|
);
|
|
if request.is_null() {
|
|
let _ = WinHttpCloseHandle(connect);
|
|
let _ = WinHttpCloseHandle(session);
|
|
return Err(b"error:winhttp-request");
|
|
}
|
|
|
|
let sent = WinHttpSendRequest(request, ptr::null(), 0, ptr::null_mut(), 0, 0, 0);
|
|
if sent == 0 || WinHttpReceiveResponse(request, ptr::null_mut()) == 0 {
|
|
close_all(request, connect, session);
|
|
return Err(b"error:winhttp-send");
|
|
}
|
|
|
|
let mut status = 0u32;
|
|
let mut status_size = mem::size_of::<Dword>() as Dword;
|
|
if WinHttpQueryHeaders(
|
|
request,
|
|
WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
|
|
ptr::null(),
|
|
ptr::addr_of_mut!(status).cast::<c_void>(),
|
|
ptr::addr_of_mut!(status_size),
|
|
ptr::null_mut(),
|
|
) == 0
|
|
{
|
|
close_all(request, connect, session);
|
|
return Err(b"error:winhttp-status");
|
|
}
|
|
|
|
let mut first_byte = 0u8;
|
|
let mut bytes_read = 0u32;
|
|
let read_ok = WinHttpReadData(
|
|
request,
|
|
ptr::addr_of_mut!(first_byte).cast::<c_void>(),
|
|
1,
|
|
ptr::addr_of_mut!(bytes_read),
|
|
);
|
|
close_all(request, connect, session);
|
|
|
|
if status != 200 {
|
|
return Err(b"error:http-status");
|
|
}
|
|
if read_ok == 0 || bytes_read == 0 {
|
|
return Err(b"error:empty-body");
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
unsafe fn close_all(request: Hinternet, connect: Hinternet, session: Hinternet) {
|
|
// SAFETY: callers pass live WinHTTP handles exactly once.
|
|
unsafe {
|
|
let _ = WinHttpCloseHandle(request);
|
|
let _ = WinHttpCloseHandle(connect);
|
|
let _ = WinHttpCloseHandle(session);
|
|
}
|
|
}
|
|
|
|
pub fn write_marker(payload: &[u8]) {
|
|
let mut configured_path = MaybeUninit::<[u8; MARKER_PATH_CAPACITY]>::uninit();
|
|
let configured_path_ptr = configured_path.as_mut_ptr().cast::<u8>();
|
|
// SAFETY: configured_path is writable for the supplied size, and all
|
|
// kernel32 pointers remain valid for the duration of each call.
|
|
unsafe {
|
|
let configured_len = GetEnvironmentVariableA(
|
|
MARKER_ENV.as_ptr().cast::<c_char>(),
|
|
configured_path_ptr.cast::<c_char>(),
|
|
MARKER_PATH_CAPACITY as Dword,
|
|
);
|
|
let path = if configured_len != 0 && configured_len < MARKER_PATH_CAPACITY as Dword {
|
|
configured_path_ptr.cast_const()
|
|
} else {
|
|
DEFAULT_MARKER.as_ptr()
|
|
};
|
|
|
|
let file = CreateFileA(
|
|
path.cast::<c_char>(),
|
|
GENERIC_WRITE,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
ptr::null_mut(),
|
|
CREATE_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
ptr::null_mut(),
|
|
);
|
|
if file == INVALID_HANDLE_VALUE {
|
|
return;
|
|
}
|
|
let mut written = 0u32;
|
|
let _ = WriteFile(
|
|
file,
|
|
payload.as_ptr().cast::<c_void>(),
|
|
payload.len() as Dword,
|
|
ptr::addr_of_mut!(written),
|
|
ptr::null_mut(),
|
|
);
|
|
let _ = CloseHandle(file);
|
|
}
|
|
}
|
|
}
|