-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Hey,
I found this while using aez as a reference point for another library and running it through a fuzzer. Basically, if the input is in an unfortunate spot (such that memory after it is unallocated), aez will attempt to read it and cause a segmentation fault:
% cargo run
Compiling crasher v0.1.0 (/home/daniel/src/aez/crasher)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.09s
Running `target/debug/crasher`
zsh: segmentation fault (core dumped) cargo run
This works for small inputs (smaller than 32 bytes, that end up in cipher_aez_tiny), as well as for large inputs that end up in cipher_aez_core:
Program received signal SIGSEGV, Segmentation fault.
0x000055555556e1ad in _mm_loadu_si128 (__P=0x7ffff7bffff1) at /usr/lib/gcc/x86_64-pc-linux-gnu/14.2.1/include/emmintrin.h:706
706 return *__P;
(gdb) bt
#0 0x000055555556e1ad in _mm_loadu_si128 (__P=0x7ffff7bffff1) at /usr/lib/gcc/x86_64-pc-linux-gnu/14.2.1/include/emmintrin.h:706
#1 loadu (p=0x7ffff7bffff1) at aez5-impls/aesni/encrypt.c:107
#2 0x0000555555574107 in cipher_aez_tiny (ctx=0x7fffffffdbc0, t=..., d=0, src=0x7ffff7bffff1 "", bytes=15, abytes=1, dst=0x5555555beb10 "") at aez5-impls/aesni/encrypt.c:623
#3 0x0000555555574ddd in aez_encrypt (ctx=0x7fffffffdbc0,
n=0x55555555d813 "\001is_aligned_to: align is not a power-of-twounsafe precondition(s) violated: ptr::write_bytes requires that the destination pointer is aligned and non-null\n\nThis indicates a bug in the program. This Un"...,
nbytes=1, ad=0x0, adbytes=0, abytes=1, src=0x7ffff7bffff1 "", bytes=15, dst=0x5555555beb10 "") at aez5-impls/aesni/encrypt.c:713
#4 0x000055555556cc4b in aez::Aez::encrypt<core::option::Option<&[u8]>> (self=0x7fffffffdbc0, n=..., ad=..., pt=..., ct=...) at /home/daniel/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aez-0.0.7/src/lib.rs:118
#5 0x000055555556d12a in crasher::main () at src/main.rs:18
Program received signal SIGSEGV, Segmentation fault.
0x000055555556e1ad in _mm_loadu_si128 (__P=0x7ffff7bffff1) at /usr/lib/gcc/x86_64-pc-linux-gnu/14.2.1/include/emmintrin.h:706
706 return *__P;
(gdb) bt
#0 0x000055555556e1ad in _mm_loadu_si128 (__P=0x7ffff7bffff1) at /usr/lib/gcc/x86_64-pc-linux-gnu/14.2.1/include/emmintrin.h:706
#1 loadu (p=0x7ffff7bffff1) at aez5-impls/aesni/encrypt.c:107
#2 0x00005555555737f3 in cipher_aez_core (ctx=0x7fffffffdbc0, t=..., d=0, src=0x7ffff7bfffc1 "", bytes=64, abytes=1, dst=0x5555555beb10 ",\341S;\004\003\362PΌ*}?\274S֎ \301(\031\f\277\356S\365N|k\253\255\267")
at aez5-impls/aesni/encrypt.c:573
#3 0x0000555555574e0c in aez_encrypt (ctx=0x7fffffffdbc0,
n=0x55555555d813 "\001is_aligned_to: align is not a power-of-twounsafe precondition(s) violated: ptr::write_bytes requires that the destination pointer is aligned and non-null\n\nThis indicates a bug in the program. This Un"...,
nbytes=1, ad=0x0, adbytes=0, abytes=1, src=0x7ffff7bfffc1 "", bytes=63, dst=0x5555555beb10 ",\341S;\004\003\362PΌ*}?\274S֎ \301(\031\f\277\356S\365N|k\253\255\267") at aez5-impls/aesni/encrypt.c:715
#4 0x000055555556cc4b in aez::Aez::encrypt<core::option::Option<&[u8]>> (self=0x7fffffffdbc0, n=..., ad=..., pt=..., ct=...) at /home/daniel/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aez-0.0.7/src/lib.rs:118
#5 0x000055555556d12a in crasher::main () at src/main.rs:18
The program to cause those is the following:
// [dependencies]
// aez = "0.0.7"
// memmap2 = "0.9.5"
use memmap2::MmapMut;
fn main() {
let key = &[255, 255, 255, 255];
let tau = 1;
const SIZE: usize = 4 * 1024 * 4096;
// Use 63 to get a crash in cipher_aez_core
// Use 15 to get a crash in cipher_aez_tiny
const OFFSET: usize = 15;
// Use mmap to get memory that likely has no neighboring pages mapped, then take the last bytes
// of this memory region as input for aez
let inbuffer = MmapMut::map_anon(SIZE).unwrap();
let inbuffer = &inbuffer[SIZE - OFFSET..];
let mut outbuffer = vec![0; inbuffer.len() + tau as usize];
aez::Aez::new(key).encrypt(&[1], None, &inbuffer, &mut outbuffer);
}I guess the issue is that encrypt.c always uses _mm_loadu_si128 to load a 16 byte block, even if only a partial (or empty) block is needed. For example, cipher_aez_tiny has
if (bytes >= 16) {
buf[0] = load(src);
buf[1] = zero_pad(load_partial(src+16,bytes-16),32-bytes);
} else {(note that load_partial is simply redefined to loadu), so even if 0 more bytes are to be loaded, the code accesses the complete next 16 bytes (out of bounds). For cipher_aez_core it's similar, as soon as there are authentication bytes, the code will read past the end of the buffer (to then discard the data and fill it with zeroes, but the read and therefore the damage is already done):
else final1 = zero_pad(loadu(src+(bytes-32)+16), abytes);Now I am aware that encrypt.c is the reference implementation that you didn't write, but I still thought it's worth to bring this issue up here since this affects your wrapper as well. And there is no repository/bug tracker for the reference implementation, so this was the closest place I could find to document this.
Edit: Ted Krovetz has confirmed that the implementation assumes pointers aligned to 16 bytes, so that no load would go past a cache line (the load may include unallocated memory, but it would not cause a fault).