Skip to content

SEGFAULT during encryption (bug in reference implementation?) #4

@Kingdread

Description

@Kingdread

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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions