use crate::builder::InitialLinuxLibcStackLayoutBuilder;
use crate::cstr_util::c_str_null_terminated;
use crate::{AuxVar, AuxVarType};
use core::mem::size_of;
pub(super) struct AuxvSerializer<'a> {
builder: &'a InitialLinuxLibcStackLayoutBuilder<'a>,
argc_write_ptr: *mut u8,
argv_key_write_ptr: *mut u8,
argv_data_write_ptr: *mut u8,
envv_key_write_ptr: *mut u8,
envv_data_write_ptr: *mut u8,
aux_key_write_ptr: *mut u8,
aux_data_write_ptr: *mut u8,
filename_write_ptr: *mut u8,
final_null_ptr: *mut u8,
user_addr: u64,
arg_write_count: usize,
env_write_count: usize,
aux_write_count: usize,
}
impl<'a> AuxvSerializer<'a> {
pub fn new(
builder: &'a InitialLinuxLibcStackLayoutBuilder,
begin_ptr: *mut u8,
user_addr: u64,
) -> Self {
unsafe {
Self {
builder,
argc_write_ptr: begin_ptr,
argv_key_write_ptr: begin_ptr.add(size_of::<u64>()),
argv_data_write_ptr: begin_ptr.add(builder.offset_to_argv_data_area()),
envv_key_write_ptr: begin_ptr.add(builder.offset_to_envv_key_area()),
envv_data_write_ptr: begin_ptr.add(builder.offset_to_env_data_area()),
aux_key_write_ptr: begin_ptr.add(builder.offset_to_aux_key_area()),
aux_data_write_ptr: begin_ptr.add(builder.offset_to_aux_data_area()),
filename_write_ptr: begin_ptr.add(builder.offset_to_filename_data_area()),
final_null_ptr: begin_ptr.add(builder.offset_to_final_null()),
user_addr,
arg_write_count: 0,
env_write_count: 0,
aux_write_count: 0,
}
}
}
pub unsafe fn write_argc(&mut self, argc: u64) {
core::ptr::write(self.argc_write_ptr.cast(), argc);
}
pub unsafe fn write_arg(&mut self, c_str: &str) {
assert!(
self.builder.arg_v.len() > self.arg_write_count,
"More arguments have been written than capacity is available!"
);
core::ptr::write(
self.argv_key_write_ptr.cast(),
self.to_user_ptr(self.argv_data_write_ptr),
);
self.argv_key_write_ptr = self.argv_key_write_ptr.add(size_of::<u64>());
core::ptr::copy_nonoverlapping(c_str.as_ptr(), self.argv_data_write_ptr, c_str.len());
self.argv_data_write_ptr = self.argv_data_write_ptr.add(c_str.len());
let write_ptr_ptr = &mut self.argv_data_write_ptr as *mut _;
self.write_cstr_null_byte_if_not_present(c_str.as_bytes(), write_ptr_ptr);
self.arg_write_count += 1;
}
pub unsafe fn write_finish_argv(&mut self) {
core::ptr::write(
self.argv_key_write_ptr.cast::<*const u8>(),
core::ptr::null(),
);
}
pub unsafe fn write_env(&mut self, c_str: &str) {
assert!(
self.builder.env_v.len() > self.env_write_count,
"More arguments have been written than capacity is available!"
);
core::ptr::write(
self.envv_key_write_ptr.cast(),
self.to_user_ptr(self.envv_data_write_ptr),
);
self.envv_key_write_ptr = self.envv_key_write_ptr.add(size_of::<u64>());
core::ptr::copy_nonoverlapping(c_str.as_ptr(), self.envv_data_write_ptr, c_str.len());
self.envv_data_write_ptr = self.envv_data_write_ptr.add(c_str.len());
let write_ptr_ptr = &mut self.envv_data_write_ptr as *mut _;
self.write_cstr_null_byte_if_not_present(c_str.as_bytes(), write_ptr_ptr);
self.env_write_count += 1;
}
pub unsafe fn write_finish_envv(&mut self) {
core::ptr::write(
self.envv_key_write_ptr.cast::<*const u8>(),
core::ptr::null(),
);
}
pub unsafe fn write_aux_entry(&mut self, aux_var: &AuxVar) {
assert!(
self.builder.aux_v.len() > self.aux_write_count,
"More arguments have been written than capacity is available!"
);
core::ptr::write(self.aux_key_write_ptr.cast(), aux_var.key().val());
self.aux_key_write_ptr = self.aux_key_write_ptr.add(size_of::<usize>());
if !aux_var.key().value_in_data_area() {
core::ptr::write(self.aux_key_write_ptr.cast::<usize>(), aux_var.value_raw());
} else {
let data_write_ptr_ptr: *mut *mut u8;
let bytes;
let is_c_str;
if aux_var.key() == AuxVarType::ExecFn {
data_write_ptr_ptr = &mut self.filename_write_ptr as *mut _;
bytes = aux_var.value_payload_cstr().unwrap().as_bytes();
is_c_str = false;
} else {
data_write_ptr_ptr = &mut self.aux_data_write_ptr as *mut _;
if let Some(cstr) = aux_var.value_payload_cstr() {
bytes = cstr.as_bytes();
is_c_str = true;
} else {
bytes = aux_var.value_payload_bytes().unwrap();
is_c_str = false;
}
}
core::ptr::write(
self.aux_key_write_ptr.cast(),
self.to_user_ptr(*data_write_ptr_ptr),
);
core::ptr::copy_nonoverlapping(bytes.as_ptr(), *data_write_ptr_ptr, bytes.len());
*data_write_ptr_ptr = (*data_write_ptr_ptr).add(bytes.len());
if is_c_str {
self.write_cstr_null_byte_if_not_present(bytes, data_write_ptr_ptr);
}
}
self.aux_key_write_ptr = self.aux_key_write_ptr.add(size_of::<usize>());
self.aux_write_count += 1;
}
pub unsafe fn write_finish(&mut self) {
core::ptr::write(self.final_null_ptr.cast::<*const u8>(), core::ptr::null());
}
unsafe fn write_cstr_null_byte_if_not_present(
&self,
bytes: &[u8],
write_ptr_ptr: *mut *mut u8,
) {
if !c_str_null_terminated(bytes) {
core::ptr::write(*write_ptr_ptr, 0);
*write_ptr_ptr = (*write_ptr_ptr).add(1);
}
}
fn get_write_ptr_offset(&self, ptr: *const u8) -> usize {
let ptr = ptr as usize;
let base = self.argc_write_ptr as usize;
ptr - base
}
fn to_user_ptr(&self, write_ptr: *const u8) -> u64 {
self.user_addr + self.get_write_ptr_offset(write_ptr) as u64
}
}
#[cfg(test)]
mod tests {
use crate::builder::serializer::AuxvSerializer;
use crate::cstr_util::cstr_len_with_nullbyte;
use crate::{AuxVar, AuxVarSerialized, AuxVarType, InitialLinuxLibcStackLayoutBuilder};
use std::mem::size_of;
#[test]
fn test_byte_writer_auxv() {
let builder = InitialLinuxLibcStackLayoutBuilder::new()
.add_aux_v(AuxVar::Clktck(0x1337))
.add_aux_v(AuxVar::Platform("x86_64"));
let mut buf = vec![0_u8; builder.total_size()];
let ptr = buf.as_ptr();
let mut writer = AuxvSerializer::new(&builder, buf.as_mut_ptr(), ptr as u64);
unsafe {
let initial_aux_data_write_ptr = writer.aux_data_write_ptr;
let mut bytes_written = 0;
for aux in builder
.aux_v
.iter()
.filter(|x| x.key().value_in_data_area())
.filter(|x| x.key() != AuxVarType::ExecFn)
{
let dst_ptr = writer.aux_data_write_ptr;
writer.write_aux_entry(aux);
bytes_written += aux.data_area_serialize_byte_count();
assert_eq!(
*writer
.aux_key_write_ptr
.sub(2 * size_of::<usize>())
.cast::<usize>(),
aux.key().val(),
"must write the correct key"
);
assert_eq!(
*writer
.aux_key_write_ptr
.sub(size_of::<usize>())
.cast::<u64>(),
dst_ptr as u64,
"must write the correct ptr"
);
assert_eq!(
initial_aux_data_write_ptr.add(bytes_written),
writer.aux_data_write_ptr,
"must update the data write ptr correctly"
);
}
}
assert!(writer.final_null_ptr > writer.filename_write_ptr);
assert_eq!(writer.filename_write_ptr, writer.envv_data_write_ptr);
assert_eq!(writer.envv_data_write_ptr, writer.argv_data_write_ptr);
assert!(writer.argv_data_write_ptr > writer.aux_data_write_ptr);
assert!(writer.aux_data_write_ptr > writer.aux_key_write_ptr);
assert!(writer.aux_key_write_ptr > writer.envv_key_write_ptr);
assert!(writer.envv_key_write_ptr > writer.argv_key_write_ptr);
assert!(writer.argv_key_write_ptr > writer.argc_write_ptr);
}
#[allow(clippy::cognitive_complexity)]
#[test]
fn test_byte_writer_full() {
let builder = InitialLinuxLibcStackLayoutBuilder::new()
.add_arg_v("arg1")
.add_arg_v("arg2\0")
.add_arg_v("arg3")
.add_env_v("ENV1=FOO1")
.add_env_v("ENV2=FOO2\0")
.add_env_v("ENV3=FOO3")
.add_aux_v(AuxVar::Clktck(0x1337))
.add_aux_v(AuxVar::Platform("x86_64"))
.add_aux_v(AuxVar::ExecFn("./executable\0"));
let mut buf = vec![0_u8; builder.total_size()];
let ptr = buf.as_ptr();
let mut writer = AuxvSerializer::new(&builder, buf.as_mut_ptr(), ptr as u64);
{
assert!(builder.offset_to_final_null() > builder.offset_to_filename_data_area());
assert!(builder.offset_to_filename_data_area() > builder.offset_to_env_data_area());
assert!(builder.offset_to_env_data_area() > builder.offset_to_argv_data_area());
assert!(builder.offset_to_argv_data_area() > builder.offset_to_aux_data_area());
assert!(builder.offset_to_aux_data_area() > builder.offset_to_aux_key_area());
assert!(builder.offset_to_aux_key_area() > builder.offset_to_envv_key_area());
assert!(builder.offset_to_envv_key_area() > builder.offset_to_argv_key_area());
}
unsafe {
writer.write_argc(3);
assert_eq!(*ptr.cast::<u64>(), 3);
}
unsafe {
let initial_argv_data_write_ptr = writer.argv_data_write_ptr;
let mut arg_byte_count = 0;
for arg in &builder.arg_v {
let previous_ptr = writer.argv_key_write_ptr;
writer.write_arg(arg);
let ptr_offset = writer.argv_key_write_ptr as usize - previous_ptr as usize;
assert_eq!(
ptr_offset, 8,
"argv_key_write_ptr must point to next ptr address"
);
arg_byte_count += cstr_len_with_nullbyte(arg.as_bytes());
let ptr_diff =
writer.argv_data_write_ptr as usize - initial_argv_data_write_ptr as usize;
assert_eq!(ptr_diff, arg_byte_count, "must write the correct amount of bytes of all c-strings for the args and update the write pointers!");
}
writer.write_finish_argv();
}
unsafe {
let initial_envv_data_write_ptr = writer.envv_data_write_ptr;
let mut env_byte_count = 0;
for env in &builder.env_v {
let previous_ptr = writer.envv_key_write_ptr;
writer.write_env(env);
let ptr_offset = writer.envv_key_write_ptr as usize - previous_ptr as usize;
assert_eq!(
ptr_offset, 8,
"envv_key_write_ptr must point to next ptr address"
);
env_byte_count += cstr_len_with_nullbyte(env.as_bytes());
let ptr_diff =
writer.envv_data_write_ptr as usize - initial_envv_data_write_ptr as usize;
assert_eq!(ptr_diff, env_byte_count, "must write the correct amount of bytes of all c-strings for the env vars and update the write pointers!");
}
writer.write_finish_envv();
}
unsafe {
assert_eq!(
writer.aux_key_write_ptr,
writer.envv_key_write_ptr.add(8),
"the first aux key follows the null ptr after the last env var key"
);
let initial_aux_data_write_ptr = writer.aux_data_write_ptr;
let mut aux_data_bytes_written = 0;
for aux in &builder.aux_v {
writer.write_aux_entry(aux);
assert_eq!(
*writer
.aux_key_write_ptr
.sub(size_of::<AuxVarSerialized>())
.cast::<usize>(),
aux.key().val(),
"must write the correct key"
);
if !aux.key().value_in_data_area() {
assert_eq!(
*writer
.aux_key_write_ptr
.sub(size_of::<AuxVarSerialized>() / 2)
.cast::<usize>(),
aux.value_raw(),
"must write the correct value"
);
} else {
let bytes_written_len = aux.data_area_serialize_byte_count();
if aux.key() == AuxVarType::ExecFn {
let slice = core::slice::from_raw_parts(
writer.filename_write_ptr.sub(bytes_written_len),
bytes_written_len,
);
assert_eq!(
aux.value_payload_cstr().unwrap().as_bytes(),
slice,
"must write the correct filename into the right location"
);
} else {
aux_data_bytes_written += bytes_written_len;
assert_eq!(
initial_aux_data_write_ptr.add(aux_data_bytes_written),
writer.aux_data_write_ptr,
"must update aux data write ptr in the correct way"
);
}
}
}
assert!(
initial_aux_data_write_ptr < writer.aux_data_write_ptr,
"there must be bytes written to the additional aux vec data area"
);
writer.write_finish();
assert_eq!(
*writer.aux_key_write_ptr.sub(size_of::<AuxVarSerialized>()) as usize,
AuxVarType::Null.val(),
"last AT var must be AtNull"
);
assert_eq!(
*writer.aux_key_write_ptr.sub(8) as u64,
0,
"AtNull var must have null as value!"
);
assert_eq!(writer.final_null_ptr, writer.filename_write_ptr);
assert!(writer.filename_write_ptr > writer.envv_data_write_ptr);
assert!(writer.envv_data_write_ptr > writer.argv_data_write_ptr);
assert!(writer.argv_data_write_ptr > writer.aux_data_write_ptr);
assert!(writer.aux_data_write_ptr > writer.aux_key_write_ptr);
assert!(writer.aux_key_write_ptr > writer.envv_key_write_ptr);
assert!(writer.envv_key_write_ptr > writer.argv_key_write_ptr);
assert!(writer.argv_key_write_ptr > writer.argc_write_ptr);
}
}
}