mirror of
https://github.com/lbr77/SideImpactor.git
synced 2026-05-06 11:14:01 -04:00
1239 lines
35 KiB
Rust
1239 lines
35 KiB
Rust
use core::ffi::{c_char, c_int, c_long, c_ulong, c_void};
|
|
use std::alloc::{alloc, alloc_zeroed, dealloc, realloc as rust_realloc, Layout};
|
|
use std::io::{ErrorKind, Read, Write};
|
|
use std::mem;
|
|
use std::ptr;
|
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
use std::sync::{Once, OnceLock};
|
|
|
|
use openssl::error::ErrorStack;
|
|
use openssl::hash::MessageDigest;
|
|
use openssl::nid::Nid;
|
|
use openssl::pkey::{HasPublic, PKey, Private};
|
|
use openssl::provider::Provider;
|
|
use openssl::rsa::Rsa;
|
|
use openssl::ssl::{
|
|
HandshakeError, MidHandshakeSslStream, Ssl, SslContextBuilder, SslMethod, SslStream,
|
|
SslVerifyMode, SslVersion,
|
|
};
|
|
use openssl::x509::extension::{BasicConstraints, KeyUsage, SubjectKeyIdentifier};
|
|
use openssl_sys as ffi;
|
|
use openssl::x509::{X509NameBuilder, X509};
|
|
use openssl::{asn1::{Asn1Integer, Asn1Time}, bn::BigNum};
|
|
use serde::Serialize;
|
|
use wasm_bindgen::prelude::*;
|
|
use web_sys::window;
|
|
|
|
static OPENSSL_PROVIDER_INIT: Once = Once::new();
|
|
static OPENSSL_PROVIDER_STATUS: OnceLock<String> = OnceLock::new();
|
|
static COUNT_QSORT: AtomicUsize = AtomicUsize::new(0);
|
|
static COUNT_VSNPRINTF: AtomicUsize = AtomicUsize::new(0);
|
|
static COUNT_SSCANF: AtomicUsize = AtomicUsize::new(0);
|
|
static COUNT_STRTOL: AtomicUsize = AtomicUsize::new(0);
|
|
static COUNT_STRTOUL: AtomicUsize = AtomicUsize::new(0);
|
|
static COUNT_GETENV: AtomicUsize = AtomicUsize::new(0);
|
|
static COUNT_OPEN: AtomicUsize = AtomicUsize::new(0);
|
|
static COUNT_READ: AtomicUsize = AtomicUsize::new(0);
|
|
static COUNT_STAT: AtomicUsize = AtomicUsize::new(0);
|
|
static COUNT_OPENDIR: AtomicUsize = AtomicUsize::new(0);
|
|
static ARC4_FALLBACK_SEED: AtomicUsize = AtomicUsize::new(0x9E37_79B9);
|
|
|
|
#[no_mangle]
|
|
pub static mut errno: c_int = 0;
|
|
|
|
#[no_mangle]
|
|
pub static CLOCK_REALTIME: c_int = 0;
|
|
|
|
#[no_mangle]
|
|
pub static _CLOCK_REALTIME: c_int = 0;
|
|
|
|
#[derive(Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct PairRecordWasmOut {
|
|
host_id: String,
|
|
system_buid: String,
|
|
host_certificate_pem: String,
|
|
host_private_key_pem: String,
|
|
root_certificate_pem: String,
|
|
root_private_key_pem: String,
|
|
device_certificate_pem: String,
|
|
}
|
|
|
|
#[derive(Copy, Clone)]
|
|
enum PairCertProfile {
|
|
Root,
|
|
Host,
|
|
Device,
|
|
}
|
|
|
|
fn bump(counter: &AtomicUsize) {
|
|
counter.fetch_add(1, Ordering::Relaxed);
|
|
}
|
|
|
|
fn ensure_openssl_providers() {
|
|
OPENSSL_PROVIDER_INIT.call_once(|| {
|
|
let mut status = Vec::new();
|
|
for name in ["default", "base", "legacy"] {
|
|
match Provider::load(None, name) {
|
|
Ok(provider) => {
|
|
// Keep providers loaded for the whole process lifetime.
|
|
mem::forget(provider);
|
|
status.push(format!("{name}=ok"));
|
|
}
|
|
Err(error) => {
|
|
status.push(format!("{name}=err({error})"));
|
|
}
|
|
}
|
|
}
|
|
let _ = OPENSSL_PROVIDER_STATUS.set(status.join(","));
|
|
});
|
|
}
|
|
|
|
fn openssl_provider_status() -> &'static str {
|
|
OPENSSL_PROVIDER_STATUS
|
|
.get()
|
|
.map(String::as_str)
|
|
.unwrap_or("<uninitialized>")
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn libimobiledevice_generate_pair_record(
|
|
device_public_key: Vec<u8>,
|
|
host_id: String,
|
|
system_buid: String,
|
|
) -> Result<String, JsValue> {
|
|
openssl::init();
|
|
ensure_openssl_providers();
|
|
|
|
let device_pubkey = parse_device_public_key(&device_public_key)
|
|
.map_err(|error| js_err("parse device public key failed", error))?;
|
|
let root_key = PKey::from_rsa(Rsa::generate(2048).map_err(|error| js_err("generate root RSA failed", error))?)
|
|
.map_err(|error| js_err("create root pkey failed", error))?;
|
|
let host_key = PKey::from_rsa(Rsa::generate(2048).map_err(|error| js_err("generate host RSA failed", error))?)
|
|
.map_err(|error| js_err("create host pkey failed", error))?;
|
|
|
|
let root_cert = build_pair_certificate(&root_key, &root_key, None, PairCertProfile::Root)
|
|
.map_err(|error| js_err("build root cert failed", error))?;
|
|
let host_cert =
|
|
build_pair_certificate(&host_key, &root_key, Some(&root_cert), PairCertProfile::Host)
|
|
.map_err(|error| js_err("build host cert failed", error))?;
|
|
let device_cert = build_pair_certificate(
|
|
&device_pubkey,
|
|
&root_key,
|
|
Some(&root_cert),
|
|
PairCertProfile::Device,
|
|
)
|
|
.map_err(|error| js_err("build device cert failed", error))?;
|
|
|
|
let root_rsa = root_key
|
|
.rsa()
|
|
.map_err(|error| js_err("extract root rsa failed", error))?;
|
|
let host_rsa = host_key
|
|
.rsa()
|
|
.map_err(|error| js_err("extract host rsa failed", error))?;
|
|
|
|
let output = PairRecordWasmOut {
|
|
host_id,
|
|
system_buid,
|
|
host_certificate_pem: pem_bytes_to_string(
|
|
host_cert
|
|
.to_pem()
|
|
.map_err(|error| js_err("export host cert failed", error))?,
|
|
)?,
|
|
host_private_key_pem: pem_bytes_to_string(
|
|
host_rsa
|
|
.private_key_to_pem()
|
|
.map_err(|error| js_err("export host key failed", error))?,
|
|
)?,
|
|
root_certificate_pem: pem_bytes_to_string(
|
|
root_cert
|
|
.to_pem()
|
|
.map_err(|error| js_err("export root cert failed", error))?,
|
|
)?,
|
|
root_private_key_pem: pem_bytes_to_string(
|
|
root_rsa
|
|
.private_key_to_pem()
|
|
.map_err(|error| js_err("export root key failed", error))?,
|
|
)?,
|
|
device_certificate_pem: pem_bytes_to_string(
|
|
device_cert
|
|
.to_pem()
|
|
.map_err(|error| js_err("export device cert failed", error))?,
|
|
)?,
|
|
};
|
|
|
|
serde_json::to_string(&output)
|
|
.map_err(|error| JsValue::from_str(&format!("serialize pair record failed: {error}")))
|
|
}
|
|
|
|
fn parse_device_public_key(device_public_key: &[u8]) -> Result<PKey<openssl::pkey::Public>, ErrorStack> {
|
|
if let Ok(key) = PKey::public_key_from_pem(device_public_key) {
|
|
return Ok(key);
|
|
}
|
|
PKey::public_key_from_der(device_public_key)
|
|
}
|
|
|
|
fn build_pair_certificate<T: HasPublic>(
|
|
subject_key: &PKey<T>,
|
|
issuer_key: &PKey<Private>,
|
|
issuer_cert: Option<&X509>,
|
|
profile: PairCertProfile,
|
|
) -> Result<X509, ErrorStack> {
|
|
let mut builder = X509::builder()?;
|
|
builder.set_version(2)?;
|
|
|
|
let serial_bn = BigNum::from_u32(0)?;
|
|
let serial = Asn1Integer::from_bn(&serial_bn)?;
|
|
builder.set_serial_number(&serial)?;
|
|
|
|
// Avoid runtime time dependencies in wasm libc shims.
|
|
let not_before = Asn1Time::from_str_x509("20240101000000Z")?;
|
|
builder.set_not_before(not_before.as_ref())?;
|
|
let not_after = Asn1Time::from_str_x509("20340101000000Z")?;
|
|
builder.set_not_after(not_after.as_ref())?;
|
|
|
|
let subject_name = X509NameBuilder::new()?.build();
|
|
builder.set_subject_name(&subject_name)?;
|
|
if let Some(cert) = issuer_cert {
|
|
builder.set_issuer_name(cert.subject_name())?;
|
|
} else {
|
|
builder.set_issuer_name(&subject_name)?;
|
|
}
|
|
builder.set_pubkey(subject_key)?;
|
|
|
|
match profile {
|
|
PairCertProfile::Root => {
|
|
let ext = BasicConstraints::new().critical().ca().build()?;
|
|
builder.append_extension(ext)?;
|
|
}
|
|
PairCertProfile::Host => {
|
|
let basic = BasicConstraints::new().critical().build()?;
|
|
builder.append_extension(basic)?;
|
|
let usage = KeyUsage::new()
|
|
.critical()
|
|
.digital_signature()
|
|
.key_encipherment()
|
|
.build()?;
|
|
builder.append_extension(usage)?;
|
|
}
|
|
PairCertProfile::Device => {
|
|
let basic = BasicConstraints::new().critical().build()?;
|
|
builder.append_extension(basic)?;
|
|
let subject_key_id =
|
|
SubjectKeyIdentifier::new().build(&builder.x509v3_context(issuer_cert.map(|v| v.as_ref()), None))?;
|
|
builder.append_extension(subject_key_id)?;
|
|
let usage = KeyUsage::new()
|
|
.critical()
|
|
.digital_signature()
|
|
.key_encipherment()
|
|
.build()?;
|
|
builder.append_extension(usage)?;
|
|
}
|
|
}
|
|
|
|
builder.sign(issuer_key, MessageDigest::sha256())?;
|
|
Ok(builder.build())
|
|
}
|
|
|
|
fn pem_bytes_to_string(value: Vec<u8>) -> Result<String, JsValue> {
|
|
let text = String::from_utf8(value)
|
|
.map_err(|error| JsValue::from_str(&format!("invalid utf8 pem: {error}")))?;
|
|
Ok(text.replace('\0', ""))
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub struct OpensslClient {
|
|
state: Option<TlsState>,
|
|
tls_out: Vec<u8>,
|
|
plain_out: Vec<u8>,
|
|
}
|
|
|
|
enum TlsState {
|
|
Handshaking(MidHandshakeSslStream<PumpIo>),
|
|
Ready(SslStream<PumpIo>),
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
impl OpensslClient {
|
|
#[wasm_bindgen(constructor)]
|
|
pub fn new(
|
|
_server_name: String,
|
|
_ca_cert_pem: String,
|
|
client_cert_pem: String,
|
|
client_key_pem: String,
|
|
) -> Result<OpensslClient, JsValue> {
|
|
openssl::init();
|
|
ensure_openssl_providers();
|
|
|
|
let cert = X509::from_pem(client_cert_pem.as_bytes())
|
|
.map_err(|error| js_err("X509::from_pem failed", error))?;
|
|
let key = PKey::private_key_from_pem(client_key_pem.as_bytes())
|
|
.map_err(|error| js_err("PKey::private_key_from_pem failed", error))?;
|
|
let cert_pubkey = cert.public_key().map_err(|error| {
|
|
JsValue::from_str(&format!(
|
|
"cert.public_key failed: {error}; providers={} cert_len={} cert_head={} cert_tail={}",
|
|
openssl_provider_status(),
|
|
client_cert_pem.len(),
|
|
pem_head_line(&client_cert_pem),
|
|
pem_tail_line(&client_cert_pem),
|
|
))
|
|
})?;
|
|
let cert_cn = cert_common_name(&cert);
|
|
let cert_pub_alg = format!("{:?}", cert_pubkey.id());
|
|
let key_alg = format!("{:?}", key.id());
|
|
let key_matches_cert = cert_pubkey.public_eq(&key);
|
|
|
|
let mut builder =
|
|
SslContextBuilder::new(SslMethod::tls()).map_err(|error| js_err("SslContextBuilder::new failed", error))?;
|
|
unsafe {
|
|
// Match libimobiledevice OpenSSL behavior for newer OpenSSL branches.
|
|
let options = ffi::SSL_OP_LEGACY_SERVER_CONNECT as u64;
|
|
let _ = ffi::SSL_CTX_set_options(builder.as_ptr(), options);
|
|
}
|
|
builder.set_security_level(0);
|
|
builder.set_verify(SslVerifyMode::NONE);
|
|
let _ = builder.set_min_proto_version(Some(SslVersion::TLS1));
|
|
builder.set_certificate(&cert).map_err(|error| {
|
|
JsValue::from_str(&format!(
|
|
"set_certificate failed: {error}; providers={} cert_cn={cert_cn} cert_pub_alg={cert_pub_alg} key_alg={key_alg} key_matches_cert={key_matches_cert} cert_len={} cert_head={} cert_tail={}",
|
|
openssl_provider_status(),
|
|
client_cert_pem.len(),
|
|
pem_head_line(&client_cert_pem),
|
|
pem_tail_line(&client_cert_pem),
|
|
))
|
|
})?;
|
|
builder
|
|
.set_private_key(&key)
|
|
.map_err(|error| js_err("set_private_key failed", error))?;
|
|
builder
|
|
.check_private_key()
|
|
.map_err(|error| js_err("check_private_key failed", error))?;
|
|
|
|
let context = builder.build();
|
|
let mut ssl = Ssl::new(&context).map_err(|error| js_err("Ssl::new failed", error))?;
|
|
ssl.set_connect_state();
|
|
ssl.set_verify(SslVerifyMode::NONE);
|
|
|
|
let io = PumpIo::new();
|
|
let state = match ssl.connect(io) {
|
|
Ok(stream) => TlsState::Ready(stream),
|
|
Err(HandshakeError::WouldBlock(mid)) => TlsState::Handshaking(mid),
|
|
Err(HandshakeError::Failure(mid)) => {
|
|
return Err(JsValue::from_str(&format!(
|
|
"initial handshake failure: {}",
|
|
mid.error()
|
|
)))
|
|
}
|
|
Err(HandshakeError::SetupFailure(error)) => {
|
|
return Err(js_err("initial handshake setup failure", error))
|
|
}
|
|
};
|
|
|
|
let mut client = OpensslClient {
|
|
state: Some(state),
|
|
tls_out: Vec::new(),
|
|
plain_out: Vec::new(),
|
|
};
|
|
client.collect_tls_out();
|
|
Ok(client)
|
|
}
|
|
|
|
pub fn is_handshaking(&self) -> bool {
|
|
matches!(self.state, Some(TlsState::Handshaking(_)))
|
|
}
|
|
|
|
pub fn write_plaintext(&mut self, data: &[u8]) -> Result<(), JsValue> {
|
|
let mut state = self.take_state()?;
|
|
match &mut state {
|
|
TlsState::Ready(stream) => {
|
|
stream
|
|
.write_all(data)
|
|
.map_err(|error| JsValue::from_str(&format!("SSL write failed: {error}")))?;
|
|
}
|
|
TlsState::Handshaking(_) => {
|
|
self.state = Some(state);
|
|
return Err(JsValue::from_str("TLS handshake is not completed"));
|
|
}
|
|
}
|
|
self.state = Some(state);
|
|
self.collect_tls_out();
|
|
Ok(())
|
|
}
|
|
|
|
pub fn feed_tls(&mut self, data: &[u8]) -> Result<(), JsValue> {
|
|
let mut state = self.take_state()?;
|
|
state.io_mut().push_inbound(data);
|
|
|
|
state = advance_handshake(state)?;
|
|
|
|
if let TlsState::Ready(stream) = &mut state {
|
|
let mut buf = [0u8; 4096];
|
|
loop {
|
|
match stream.read(&mut buf) {
|
|
Ok(0) => break,
|
|
Ok(read) => self.plain_out.extend_from_slice(&buf[..read]),
|
|
Err(error) if error.kind() == ErrorKind::WouldBlock => break,
|
|
Err(error) => {
|
|
self.state = Some(state);
|
|
return Err(JsValue::from_str(&format!("SSL read failed: {error}")));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
self.state = Some(state);
|
|
self.collect_tls_out();
|
|
Ok(())
|
|
}
|
|
|
|
pub fn take_tls_out(&mut self) -> Vec<u8> {
|
|
mem::take(&mut self.tls_out)
|
|
}
|
|
|
|
pub fn take_plain_out(&mut self) -> Vec<u8> {
|
|
mem::take(&mut self.plain_out)
|
|
}
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn debug_runtime_stats() -> String {
|
|
format!(
|
|
"providers={}; qsort={} vsnprintf={} sscanf={} strtol={} strtoul={} getenv={} open={} read={} stat={} opendir={}",
|
|
openssl_provider_status(),
|
|
COUNT_QSORT.load(Ordering::Relaxed),
|
|
COUNT_VSNPRINTF.load(Ordering::Relaxed),
|
|
COUNT_SSCANF.load(Ordering::Relaxed),
|
|
COUNT_STRTOL.load(Ordering::Relaxed),
|
|
COUNT_STRTOUL.load(Ordering::Relaxed),
|
|
COUNT_GETENV.load(Ordering::Relaxed),
|
|
COUNT_OPEN.load(Ordering::Relaxed),
|
|
COUNT_READ.load(Ordering::Relaxed),
|
|
COUNT_STAT.load(Ordering::Relaxed),
|
|
COUNT_OPENDIR.load(Ordering::Relaxed),
|
|
)
|
|
}
|
|
|
|
impl OpensslClient {
|
|
fn take_state(&mut self) -> Result<TlsState, JsValue> {
|
|
self.state
|
|
.take()
|
|
.ok_or_else(|| JsValue::from_str("TLS state is unavailable"))
|
|
}
|
|
|
|
fn collect_tls_out(&mut self) {
|
|
if let Some(state) = &mut self.state {
|
|
let bytes = state.io_mut().take_outbound();
|
|
if !bytes.is_empty() {
|
|
self.tls_out.extend_from_slice(&bytes);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TlsState {
|
|
fn io_mut(&mut self) -> &mut PumpIo {
|
|
match self {
|
|
TlsState::Handshaking(mid) => mid.get_mut(),
|
|
TlsState::Ready(stream) => stream.get_mut(),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn advance_handshake(mut state: TlsState) -> Result<TlsState, JsValue> {
|
|
loop {
|
|
match state {
|
|
TlsState::Handshaking(mid) => match mid.handshake() {
|
|
Ok(stream) => {
|
|
state = TlsState::Ready(stream);
|
|
}
|
|
Err(HandshakeError::WouldBlock(next_mid)) => {
|
|
return Ok(TlsState::Handshaking(next_mid));
|
|
}
|
|
Err(HandshakeError::Failure(failed_mid)) => {
|
|
return Err(JsValue::from_str(&format!(
|
|
"handshake failure: {}",
|
|
failed_mid.error()
|
|
)))
|
|
}
|
|
Err(HandshakeError::SetupFailure(error)) => {
|
|
return Err(js_err("handshake setup failure", error))
|
|
}
|
|
},
|
|
TlsState::Ready(_) => return Ok(state),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn js_err(prefix: &str, error: ErrorStack) -> JsValue {
|
|
JsValue::from_str(&format!("{prefix}: {error}"))
|
|
}
|
|
|
|
fn pem_head_line(pem: &str) -> &str {
|
|
pem.lines()
|
|
.find(|line| !line.trim().is_empty())
|
|
.unwrap_or("<empty>")
|
|
}
|
|
|
|
fn pem_tail_line(pem: &str) -> &str {
|
|
pem.lines()
|
|
.rev()
|
|
.find(|line| !line.trim().is_empty())
|
|
.unwrap_or("<empty>")
|
|
}
|
|
|
|
fn cert_common_name(cert: &X509) -> String {
|
|
cert.subject_name()
|
|
.entries_by_nid(Nid::COMMONNAME)
|
|
.next()
|
|
.and_then(|entry| entry.data().as_utf8().ok().map(|v| v.to_string()))
|
|
.unwrap_or_else(|| "<none>".to_string())
|
|
}
|
|
|
|
struct PumpIo {
|
|
inbound: Vec<u8>,
|
|
outbound: Vec<u8>,
|
|
}
|
|
|
|
impl PumpIo {
|
|
fn new() -> Self {
|
|
Self {
|
|
inbound: Vec::new(),
|
|
outbound: Vec::new(),
|
|
}
|
|
}
|
|
|
|
fn push_inbound(&mut self, data: &[u8]) {
|
|
if !data.is_empty() {
|
|
self.inbound.extend_from_slice(data);
|
|
}
|
|
}
|
|
|
|
fn take_outbound(&mut self) -> Vec<u8> {
|
|
mem::take(&mut self.outbound)
|
|
}
|
|
}
|
|
|
|
impl Read for PumpIo {
|
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
|
if self.inbound.is_empty() {
|
|
return Err(std::io::Error::from(ErrorKind::WouldBlock));
|
|
}
|
|
let read_len = buf.len().min(self.inbound.len());
|
|
buf[..read_len].copy_from_slice(&self.inbound[..read_len]);
|
|
self.inbound.drain(..read_len);
|
|
Ok(read_len)
|
|
}
|
|
}
|
|
|
|
impl Write for PumpIo {
|
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
|
if !buf.is_empty() {
|
|
self.outbound.extend_from_slice(buf);
|
|
}
|
|
Ok(buf.len())
|
|
}
|
|
|
|
fn flush(&mut self) -> std::io::Result<()> {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[repr(C)]
|
|
struct AllocHeader {
|
|
payload_size: usize,
|
|
align: usize,
|
|
}
|
|
|
|
fn alloc_payload(size: usize, align: usize, zeroed: bool) -> *mut c_void {
|
|
let safe_align = align.max(mem::align_of::<AllocHeader>()).max(8);
|
|
let total_size = match size.checked_add(mem::size_of::<AllocHeader>()) {
|
|
Some(value) => value,
|
|
None => return ptr::null_mut(),
|
|
};
|
|
let layout = match Layout::from_size_align(total_size, safe_align) {
|
|
Ok(value) => value,
|
|
Err(_) => return ptr::null_mut(),
|
|
};
|
|
let raw = unsafe {
|
|
if zeroed {
|
|
alloc_zeroed(layout)
|
|
} else {
|
|
alloc(layout)
|
|
}
|
|
};
|
|
if raw.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
unsafe {
|
|
let header = raw as *mut AllocHeader;
|
|
(*header).payload_size = size;
|
|
(*header).align = safe_align;
|
|
raw.add(mem::size_of::<AllocHeader>()) as *mut c_void
|
|
}
|
|
}
|
|
|
|
unsafe fn header_from_payload(payload: *mut c_void) -> *mut AllocHeader {
|
|
(payload as *mut u8).sub(mem::size_of::<AllocHeader>()) as *mut AllocHeader
|
|
}
|
|
|
|
unsafe fn c_strlen(ptr_in: *const c_char) -> usize {
|
|
if ptr_in.is_null() {
|
|
return 0;
|
|
}
|
|
let mut len = 0usize;
|
|
let mut cursor = ptr_in;
|
|
while *cursor != 0 {
|
|
len += 1;
|
|
cursor = cursor.add(1);
|
|
}
|
|
len
|
|
}
|
|
|
|
unsafe fn c_strcmp(a: *const c_char, b: *const c_char, limit: Option<usize>) -> c_int {
|
|
let mut index = 0usize;
|
|
loop {
|
|
if let Some(max_len) = limit {
|
|
if index >= max_len {
|
|
return 0;
|
|
}
|
|
}
|
|
let av = if a.is_null() { 0 } else { *a.add(index) as i32 };
|
|
let bv = if b.is_null() { 0 } else { *b.add(index) as i32 };
|
|
if av != bv {
|
|
return av - bv;
|
|
}
|
|
if av == 0 {
|
|
return 0;
|
|
}
|
|
index += 1;
|
|
}
|
|
}
|
|
|
|
unsafe fn c_find_char(haystack: *const c_char, needle: c_int, from_end: bool) -> *mut c_char {
|
|
if haystack.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
let n = (needle & 0xff) as u8;
|
|
if from_end {
|
|
let len = c_strlen(haystack);
|
|
let mut index = len;
|
|
loop {
|
|
let value = *haystack.add(index) as u8;
|
|
if value == n {
|
|
return haystack.add(index) as *mut c_char;
|
|
}
|
|
if index == 0 {
|
|
break;
|
|
}
|
|
index -= 1;
|
|
}
|
|
return ptr::null_mut();
|
|
}
|
|
let mut index = 0usize;
|
|
loop {
|
|
let value = *haystack.add(index) as u8;
|
|
if value == n {
|
|
return haystack.add(index) as *mut c_char;
|
|
}
|
|
if value == 0 {
|
|
return ptr::null_mut();
|
|
}
|
|
index += 1;
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn malloc(size: usize) -> *mut c_void {
|
|
alloc_payload(size, 16, false)
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn calloc(count: usize, size: usize) -> *mut c_void {
|
|
let total = match count.checked_mul(size) {
|
|
Some(value) => value,
|
|
None => return ptr::null_mut(),
|
|
};
|
|
alloc_payload(total, 16, true)
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn realloc(ptr_in: *mut c_void, size: usize) -> *mut c_void {
|
|
if ptr_in.is_null() {
|
|
return malloc(size);
|
|
}
|
|
unsafe {
|
|
let old_header = header_from_payload(ptr_in);
|
|
let old_payload_size = (*old_header).payload_size;
|
|
let align = (*old_header).align;
|
|
let old_total = old_payload_size + mem::size_of::<AllocHeader>();
|
|
let new_total = match size.checked_add(mem::size_of::<AllocHeader>()) {
|
|
Some(value) => value,
|
|
None => return ptr::null_mut(),
|
|
};
|
|
let layout = match Layout::from_size_align(old_total, align) {
|
|
Ok(value) => value,
|
|
Err(_) => return ptr::null_mut(),
|
|
};
|
|
let new_raw = rust_realloc(old_header as *mut u8, layout, new_total);
|
|
if new_raw.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
let new_header = new_raw as *mut AllocHeader;
|
|
(*new_header).payload_size = size;
|
|
(*new_header).align = align;
|
|
new_raw.add(mem::size_of::<AllocHeader>()) as *mut c_void
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn free(ptr_in: *mut c_void) {
|
|
if ptr_in.is_null() {
|
|
return;
|
|
}
|
|
unsafe {
|
|
let header = header_from_payload(ptr_in);
|
|
let payload_size = (*header).payload_size;
|
|
let align = (*header).align;
|
|
let total = payload_size + mem::size_of::<AllocHeader>();
|
|
if let Ok(layout) = Layout::from_size_align(total, align) {
|
|
dealloc(header as *mut u8, layout);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn posix_memalign(out: *mut *mut c_void, align: usize, size: usize) -> c_int {
|
|
if out.is_null() {
|
|
return 22;
|
|
}
|
|
let ptr_out = alloc_payload(size, align.max(16), false);
|
|
if ptr_out.is_null() {
|
|
return 12;
|
|
}
|
|
unsafe {
|
|
*out = ptr_out;
|
|
}
|
|
0
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn memchr(ptr_in: *const c_void, value: c_int, len: usize) -> *mut c_void {
|
|
if ptr_in.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
let needle = (value & 0xff) as u8;
|
|
unsafe {
|
|
let bytes = std::slice::from_raw_parts(ptr_in as *const u8, len);
|
|
for (index, byte) in bytes.iter().enumerate() {
|
|
if *byte == needle {
|
|
return (ptr_in as *const u8).add(index) as *mut c_void;
|
|
}
|
|
}
|
|
}
|
|
ptr::null_mut()
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn strcmp(a: *const c_char, b: *const c_char) -> c_int {
|
|
unsafe { c_strcmp(a, b, None) }
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn strncmp(a: *const c_char, b: *const c_char, n: usize) -> c_int {
|
|
unsafe { c_strcmp(a, b, Some(n)) }
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn strchr(haystack: *const c_char, needle: c_int) -> *mut c_char {
|
|
unsafe { c_find_char(haystack, needle, false) }
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn strrchr(haystack: *const c_char, needle: c_int) -> *mut c_char {
|
|
unsafe { c_find_char(haystack, needle, true) }
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn strcpy(dst: *mut c_char, src: *const c_char) -> *mut c_char {
|
|
if dst.is_null() || src.is_null() {
|
|
return dst;
|
|
}
|
|
unsafe {
|
|
let mut index = 0usize;
|
|
loop {
|
|
let byte = *src.add(index);
|
|
*dst.add(index) = byte;
|
|
if byte == 0 {
|
|
break;
|
|
}
|
|
index += 1;
|
|
}
|
|
}
|
|
dst
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn strncpy(dst: *mut c_char, src: *const c_char, n: usize) -> *mut c_char {
|
|
if dst.is_null() || src.is_null() {
|
|
return dst;
|
|
}
|
|
unsafe {
|
|
let mut index = 0usize;
|
|
while index < n {
|
|
let byte = *src.add(index);
|
|
*dst.add(index) = byte;
|
|
index += 1;
|
|
if byte == 0 {
|
|
break;
|
|
}
|
|
}
|
|
while index < n {
|
|
*dst.add(index) = 0;
|
|
index += 1;
|
|
}
|
|
}
|
|
dst
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn strstr(haystack: *const c_char, needle: *const c_char) -> *mut c_char {
|
|
if haystack.is_null() || needle.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
unsafe {
|
|
let needle_len = c_strlen(needle);
|
|
if needle_len == 0 {
|
|
return haystack as *mut c_char;
|
|
}
|
|
let hay_len = c_strlen(haystack);
|
|
if hay_len < needle_len {
|
|
return ptr::null_mut();
|
|
}
|
|
for start in 0..=(hay_len - needle_len) {
|
|
if c_strcmp(haystack.add(start), needle, Some(needle_len)) == 0 {
|
|
return haystack.add(start) as *mut c_char;
|
|
}
|
|
}
|
|
}
|
|
ptr::null_mut()
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn strpbrk(haystack: *const c_char, accept: *const c_char) -> *mut c_char {
|
|
if haystack.is_null() || accept.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
unsafe {
|
|
let mut index = 0usize;
|
|
loop {
|
|
let ch = *haystack.add(index);
|
|
if ch == 0 {
|
|
break;
|
|
}
|
|
if !c_find_char(accept, ch as c_int, false).is_null() {
|
|
return haystack.add(index) as *mut c_char;
|
|
}
|
|
index += 1;
|
|
}
|
|
}
|
|
ptr::null_mut()
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn strspn(s: *const c_char, accept: *const c_char) -> usize {
|
|
if s.is_null() || accept.is_null() {
|
|
return 0;
|
|
}
|
|
unsafe {
|
|
let mut count = 0usize;
|
|
loop {
|
|
let ch = *s.add(count);
|
|
if ch == 0 {
|
|
return count;
|
|
}
|
|
if c_find_char(accept, ch as c_int, false).is_null() {
|
|
return count;
|
|
}
|
|
count += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn strcspn(s: *const c_char, reject: *const c_char) -> usize {
|
|
if s.is_null() || reject.is_null() {
|
|
return 0;
|
|
}
|
|
unsafe {
|
|
let mut count = 0usize;
|
|
loop {
|
|
let ch = *s.add(count);
|
|
if ch == 0 {
|
|
return count;
|
|
}
|
|
if !c_find_char(reject, ch as c_int, false).is_null() {
|
|
return count;
|
|
}
|
|
count += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn atoi(s: *const c_char) -> c_int {
|
|
strtol(s, ptr::null_mut(), 10) as c_int
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn strtol(s: *const c_char, endptr: *mut *mut c_char, base: c_int) -> c_long {
|
|
bump(&COUNT_STRTOL);
|
|
let (signed, value, parsed, end) = unsafe { parse_c_integer(s, base) };
|
|
if !endptr.is_null() {
|
|
unsafe {
|
|
*endptr = if parsed { end as *mut c_char } else { s as *mut c_char };
|
|
}
|
|
}
|
|
if !parsed {
|
|
return 0;
|
|
}
|
|
|
|
if signed {
|
|
let neg = value as i128;
|
|
let v = -neg;
|
|
if v < c_long::MIN as i128 {
|
|
return c_long::MIN;
|
|
}
|
|
return v as c_long;
|
|
}
|
|
|
|
if value > c_long::MAX as u128 {
|
|
return c_long::MAX;
|
|
}
|
|
value as c_long
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn strtoul(s: *const c_char, endptr: *mut *mut c_char, base: c_int) -> c_ulong {
|
|
bump(&COUNT_STRTOUL);
|
|
let (signed, value, parsed, end) = unsafe { parse_c_integer(s, base) };
|
|
if !endptr.is_null() {
|
|
unsafe {
|
|
*endptr = if parsed { end as *mut c_char } else { s as *mut c_char };
|
|
}
|
|
}
|
|
if !parsed {
|
|
return 0;
|
|
}
|
|
if signed {
|
|
let wrapped = (0u128).wrapping_sub(value) as u128;
|
|
return wrapped as c_ulong;
|
|
}
|
|
if value > c_ulong::MAX as u128 {
|
|
return c_ulong::MAX;
|
|
}
|
|
value as c_ulong
|
|
}
|
|
|
|
unsafe fn parse_c_integer(
|
|
s: *const c_char,
|
|
base_in: c_int,
|
|
) -> (bool, u128, bool, *const c_char) {
|
|
if s.is_null() {
|
|
return (false, 0, false, s);
|
|
}
|
|
let mut p = s as *const u8;
|
|
|
|
while !p.is_null() {
|
|
let ch = *p;
|
|
if ch == b' ' || (b'\t'..=b'\r').contains(&ch) {
|
|
p = p.add(1);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
let mut negative = false;
|
|
if !p.is_null() {
|
|
match *p {
|
|
b'-' => {
|
|
negative = true;
|
|
p = p.add(1);
|
|
}
|
|
b'+' => {
|
|
p = p.add(1);
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
let mut base = base_in as i32;
|
|
if base == 0 {
|
|
if *p == b'0' {
|
|
let next = *p.add(1);
|
|
if next == b'x' || next == b'X' {
|
|
base = 16;
|
|
p = p.add(2);
|
|
} else {
|
|
base = 8;
|
|
p = p.add(1);
|
|
}
|
|
} else {
|
|
base = 10;
|
|
}
|
|
} else if base == 16 {
|
|
if *p == b'0' {
|
|
let next = *p.add(1);
|
|
if next == b'x' || next == b'X' {
|
|
p = p.add(2);
|
|
}
|
|
}
|
|
}
|
|
|
|
if !(2..=36).contains(&base) {
|
|
return (negative, 0, false, s);
|
|
}
|
|
|
|
let base_u = base as u128;
|
|
let mut value: u128 = 0;
|
|
let mut parsed = false;
|
|
let mut cursor = p;
|
|
loop {
|
|
let ch = *cursor;
|
|
if ch == 0 {
|
|
break;
|
|
}
|
|
let digit = match ch {
|
|
b'0'..=b'9' => (ch - b'0') as u32,
|
|
b'a'..=b'z' => (ch - b'a' + 10) as u32,
|
|
b'A'..=b'Z' => (ch - b'A' + 10) as u32,
|
|
_ => break,
|
|
};
|
|
if digit >= base as u32 {
|
|
break;
|
|
}
|
|
parsed = true;
|
|
value = value
|
|
.saturating_mul(base_u)
|
|
.saturating_add(digit as u128);
|
|
cursor = cursor.add(1);
|
|
}
|
|
|
|
if !parsed {
|
|
return (negative, 0, false, s);
|
|
}
|
|
(negative, value, true, cursor as *const c_char)
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn tolower(value: c_int) -> c_int {
|
|
if (65..=90).contains(&value) {
|
|
value + 32
|
|
} else {
|
|
value
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn qsort(
|
|
base: *mut c_void,
|
|
nmemb: usize,
|
|
size: usize,
|
|
compar: extern "C" fn(*const c_void, *const c_void) -> c_int,
|
|
) {
|
|
bump(&COUNT_QSORT);
|
|
if base.is_null() || size == 0 || nmemb < 2 {
|
|
return;
|
|
}
|
|
unsafe {
|
|
let base_ptr = base as *mut u8;
|
|
let mut tmp = vec![0u8; size];
|
|
for i in 1..nmemb {
|
|
let mut j = i;
|
|
while j > 0 {
|
|
let left = base_ptr.add((j - 1) * size) as *const c_void;
|
|
let right = base_ptr.add(j * size) as *const c_void;
|
|
if compar(left, right) <= 0 {
|
|
break;
|
|
}
|
|
ptr::copy_nonoverlapping(base_ptr.add((j - 1) * size), tmp.as_mut_ptr(), size);
|
|
ptr::copy(
|
|
base_ptr.add(j * size),
|
|
base_ptr.add((j - 1) * size),
|
|
size,
|
|
);
|
|
ptr::copy_nonoverlapping(tmp.as_ptr(), base_ptr.add(j * size), size);
|
|
j -= 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn sscanf(_src: *const c_char, _format: *const c_char, _arg: *mut c_void) -> c_int {
|
|
bump(&COUNT_SSCANF);
|
|
0
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn getenv(_name: *const c_char) -> *mut c_char {
|
|
bump(&COUNT_GETENV);
|
|
ptr::null_mut()
|
|
}
|
|
|
|
static ERRNO_TEXT: &[u8] = b"error\0";
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn strerror(_errnum: c_int) -> *mut c_char {
|
|
ERRNO_TEXT.as_ptr() as *mut c_char
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn getpid() -> c_int {
|
|
1
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn abort() {
|
|
panic!("abort called")
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn __cxa_atexit(
|
|
_func: extern "C" fn(*mut c_void),
|
|
_arg: *mut c_void,
|
|
_dso: *mut c_void,
|
|
) -> c_int {
|
|
0
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn time(out: *mut i64) -> i64 {
|
|
let now = 0i64;
|
|
if !out.is_null() {
|
|
unsafe {
|
|
*out = now;
|
|
}
|
|
}
|
|
now
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn gettimeofday(tv: *mut c_void, _tz: *mut c_void) -> c_int {
|
|
if tv.is_null() {
|
|
return -1;
|
|
}
|
|
unsafe {
|
|
let seconds = tv as *mut i64;
|
|
let micros = seconds.add(1);
|
|
*seconds = 0;
|
|
*micros = 0;
|
|
}
|
|
0
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn clock_gettime(_clock_id: c_int, tp: *mut c_void) -> c_int {
|
|
if tp.is_null() {
|
|
return -1;
|
|
}
|
|
unsafe {
|
|
let sec = tp as *mut i64;
|
|
let nsec = sec.add(1);
|
|
*sec = 0;
|
|
*nsec = 0;
|
|
}
|
|
0
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn arc4random_buf(buf: *mut c_void, len: usize) {
|
|
if buf.is_null() {
|
|
return;
|
|
}
|
|
unsafe {
|
|
let out = std::slice::from_raw_parts_mut(buf as *mut u8, len);
|
|
if fill_random_with_webcrypto(out) {
|
|
return;
|
|
}
|
|
|
|
// Fallback for non-browser runtime paths.
|
|
let mut state = (ARC4_FALLBACK_SEED.fetch_add(0x7f4a7c15, Ordering::Relaxed) as u64)
|
|
^ ((out.as_ptr() as usize as u64) << 1)
|
|
^ (len as u64).wrapping_mul(0x9e3779b97f4a7c15);
|
|
for byte in out.iter_mut() {
|
|
state ^= state << 13;
|
|
state ^= state >> 7;
|
|
state ^= state << 17;
|
|
*byte = (state & 0xff) as u8;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn fill_random_with_webcrypto(out: &mut [u8]) -> bool {
|
|
if out.is_empty() {
|
|
return true;
|
|
}
|
|
if let Some(win) = window() {
|
|
if let Ok(crypto) = win.crypto() {
|
|
// Browser RNG source, equivalent to native secure random for this runtime.
|
|
return crypto.get_random_values_with_u8_array(out).is_ok();
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn getentropy(buf: *mut c_void, len: usize) -> c_int {
|
|
arc4random_buf(buf, len);
|
|
0
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn open(_path: *const c_char, _flags: c_int, _mode: c_int) -> c_int {
|
|
bump(&COUNT_OPEN);
|
|
-1
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn read(_fd: c_int, _buf: *mut c_void, _count: usize) -> c_int {
|
|
bump(&COUNT_READ);
|
|
-1
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn close(_fd: c_int) -> c_int {
|
|
0
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn stat(_path: *const c_char, _out: *mut c_void) -> c_int {
|
|
bump(&COUNT_STAT);
|
|
-1
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn fstat(_fd: c_int, _out: *mut c_void) -> c_int {
|
|
-1
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn opendir(_name: *const c_char) -> *mut c_void {
|
|
bump(&COUNT_OPENDIR);
|
|
ptr::null_mut()
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn readdir(_dir: *mut c_void) -> *mut c_void {
|
|
ptr::null_mut()
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn closedir(_dir: *mut c_void) -> c_int {
|
|
0
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn SSL_add_dir_cert_subjects_to_stack(_stack: *mut c_void, _dir: *const c_char) -> c_int {
|
|
1
|
|
}
|