Skip to content

Commit

Permalink
Retrieve the actual alignment of max_align_t
Browse files Browse the repository at this point in the history
The code had a hard-coded maximum alignment of 16 (128 bits) which may
have potentially wasted memory and cause memory unsafety if it was ever
compiled with larger alignment (unlikely).

This change implements a trick to retrieve the actual alignment from C.
To achieve this a newly added C file defines a symbol initialized to
`_Alignof(max_align_t)`. It is then compiled (as a separate object), the
contents of the symbol is extracted and converted to decimal string
which is injected into the `AlignedType` definition. The definition is
generated as a separate `.rs` file in `OUT_DIR` and included into
`types.rs`.
  • Loading branch information
Kixunil committed Dec 4, 2022
1 parent 5256139 commit dbe3a34
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 16 deletions.
92 changes: 85 additions & 7 deletions secp256k1-sys/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,77 @@ extern crate cc;

use std::env;

fn main() {
// Actual build
fn gen_max_align() {
configured_cc()
.file("depend/max_align.c")
.cargo_metadata(false)
.compile("max_align.o");
let out_dir = std::path::PathBuf::from(std::env::var_os("OUT_DIR").expect("missing OUT_DIR"));
let target_endian = std::env::var("CARGO_CFG_TARGET_ENDIAN")
.expect("missing CARGO_CFG_TARGET_ENDIAN");
let target_pointer_width_bytes = std::env::var("CARGO_CFG_TARGET_POINTER_WIDTH")
.expect("missing CARGO_CFG_TARGET_POINTER_WIDTH")
.parse::<usize>()
.expect("malformed CARGO_CFG_TARGET_POINTER_WIDTH")
// CARGO_CFG_TARGET_POINTER_WIDTH is in bits, we want bytes
/ 8;
let max_align_bin = out_dir.join("max_align.bin");
// Note that this copies *whole* sections to a binary file.
// It's a bit brittle because some other symbol could theoretically end up there.
// Currently it only has one on my machine and we guard against unexpected changes by checking
// the size - it must match the target pointer width.
let objcopy = std::process::Command::new("objcopy")
.args(&["-O", "binary"])
// cc inserts depend - WTF
.arg(out_dir.join("depend/max_align.o"))
.arg(&max_align_bin)
.spawn()
.expect("failed to run objcopy")
.wait()
.expect("failed to wait for objcopy");
assert!(objcopy.success(), "objcopy failed");
let mut max_align_bytes = std::fs::read(max_align_bin).expect("failed to read max_align.bin");
// The `usize` of target and host may not match so we need to do conversion.
// Sensible alignments should be very small anyway but we don't want crappy `unsafe` code.
// Little endian happens to be a bit easier to process so we convert into that.
// If the type is smaller than `u64` we zero-pad it.
// If the type is larger than `u6` but the number fits into `u64` it'll have
// unused tail which is easy to cut-off.
// If the number is larger than `u64::MAX` then bytes beyond `u64` size will
// be non-zero.
//
// So as long as the max alignment fits into `u64` this can decode alignment
// for any architecture on any architecture.
assert_eq!(max_align_bytes.len(), target_pointer_width_bytes);
if target_endian != "little" {
max_align_bytes.reverse()
}
// copying like this auto-pads the number with zeroes
let mut buf = [0; std::mem::size_of::<u64>()];
let to_copy = buf.len().min(max_align_bytes.len());
// Overflow check
if max_align_bytes[to_copy..].iter().any(|b| *b != 0) {
panic!("max alignment overflowed u64");
}
buf[..to_copy].copy_from_slice(&max_align_bytes[..to_copy]);
let max_align = u64::from_le_bytes(buf);
let src = format!(r#"
/// A type that is as aligned as the biggest alignment for fundamental types in C.
///
/// Since C11 that means as aligned as `max_align_t` is.
/// The exact size/alignment is unspecified.
#[repr(align({}))]
#[derive(Default, Copy, Clone)]
pub struct AlignedType([u8; {}]);"#, max_align, max_align);
std::fs::write(out_dir.join("aligned_type.rs"), src.as_bytes()).expect("failed to write aligned_type.rs");
}

/// Returns CC builder configured with all defines but no C files.
fn configured_cc() -> cc::Build {
// While none of these currently affect max alignment we prefer to keep the "hygiene" so that
// new code will be correct.
let mut base_config = cc::Build::new();
base_config.include("depend/secp256k1/")
.include("depend/secp256k1/include")
.include("depend/secp256k1/src")
.flag_if_supported("-Wno-unused-function") // some ecmult stuff is defined but not used upstream
.define("SECP256K1_API", Some(""))
base_config.define("SECP256K1_API", Some(""))
.define("ENABLE_MODULE_ECDH", Some("1"))
.define("ENABLE_MODULE_SCHNORRSIG", Some("1"))
.define("ENABLE_MODULE_EXTRAKEYS", Some("1"));
Expand All @@ -48,6 +111,17 @@ fn main() {
#[cfg(feature = "recovery")]
base_config.define("ENABLE_MODULE_RECOVERY", Some("1"));

base_config
}

fn build_secp256k1() {
let mut base_config = configured_cc();
base_config.include("depend/secp256k1/")
.include("depend/secp256k1/include")
.include("depend/secp256k1/src")
.flag_if_supported("-Wno-unused-function"); // some ecmult stuff is defined but not used upstream


// WASM headers and size/align defines.
if env::var("CARGO_CFG_TARGET_ARCH").unwrap() == "wasm32" {
base_config.include("wasm/wasm-sysroot")
Expand All @@ -69,3 +143,7 @@ fn main() {
}
}

fn main() {
gen_max_align();
build_secp256k1();
}
5 changes: 5 additions & 0 deletions secp256k1-sys/depend/max_align.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#include <stddef.h>

// Note that this symbol is NOT linked with the rest of the library.
// The name is sort of unique in case it accidentally gets linked.
const size_t rust_secp256k1_private_max_align = _Alignof(max_align_t);
12 changes: 3 additions & 9 deletions secp256k1-sys/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,15 @@ pub type c_char = i8;

pub use core::ffi::c_void;

/// A type that is as aligned as the biggest alignment for fundamental types in C
/// since C11 that means as aligned as `max_align_t` is.
/// the exact size/alignment is unspecified.
// 16 matches is as big as the biggest alignment in any arch that rust currently supports https://github.com/rust-lang/rust/blob/2c31b45ae878b821975c4ebd94cc1e49f6073fd0/library/std/src/sys_common/alloc.rs
#[repr(align(16))]
#[derive(Default, Copy, Clone)]
pub struct AlignedType([u8; 16]);
include!(concat!(env!("OUT_DIR"), "/aligned_type.rs"));

impl AlignedType {
pub fn zeroed() -> Self {
AlignedType([0u8; 16])
Self::ZERO
}

/// A static zeroed out AlignedType for use in static assignments of [AlignedType; _]
pub const ZERO: AlignedType = AlignedType([0u8; 16]);
pub const ZERO: AlignedType = AlignedType([0u8; core::mem::size_of::<AlignedType>()]);
}

#[cfg(all(feature = "alloc", not(rust_secp_no_symbol_renaming)))]
Expand Down

0 comments on commit dbe3a34

Please sign in to comment.