Skip to content

Design: Arithmetic Overflow in the wrapper, how do we deal with it? #2388

@a4lg

Description

@a4lg

Some wrappers inside the windows crate takes one or more slices as arguments.

What I concern is, on current windows crate, slice.len() is converted to another type without checking validity against the target type. As a result, it sometimes causes arithmetic overflow.

For instance, MultiByteToWideChar Win32 API uses int as the length type.

Quoting crates/libs/windows/src/Windows/Win32/Globalization/mod.rs:

#[inline]
pub unsafe fn MultiByteToWideChar(codepage: u32, dwflags: MULTI_BYTE_TO_WIDE_CHAR_FLAGS, lpmultibytestr: &[u8], lpwidecharstr: ::core::option::Option<&mut [u16]>) -> i32 {
    ::windows::imp::link ! ( "kernel32.dll""system" fn MultiByteToWideChar ( codepage : u32 , dwflags : MULTI_BYTE_TO_WIDE_CHAR_FLAGS , lpmultibytestr : :: windows::core::PCSTR , cbmultibyte : i32 , lpwidecharstr : :: windows::core::PWSTR , cchwidechar : i32 ) -> i32 );
    MultiByteToWideChar(codepage, dwflags, ::core::mem::transmute(lpmultibytestr.as_ptr()), lpmultibytestr.len() as _, ::core::mem::transmute(lpwidecharstr.as_deref().map_or(::core::ptr::null(), |slice| slice.as_ptr())), lpwidecharstr.as_deref().map_or(0, |slice| slice.len() as _))
}

OK, if we overflow i32, that would be a problem.

Following test code intentionally exploits this and try to pass 0x1_0000_0003-byte sized buffer to MultiByteToWideChar. It is handled as 3-byte sized buffer in the wrapper and the result is corrupted.

For windows-sys crate, I don't find any issues. The user must handle such conditions to use the API safely. However, in windows crate, slice length type is hidden inside the wrapper and there's no safety measures.

I'm not sure what to do but I can say that some decision making will be required.

// Run this code on the 64-bit environment!

use windows::Win32::Globalization::*;
use windows::Win32::Foundation::*;

fn main() {
    // Japanese Shift_JIS (Microsoft variant)
    const CODE_PAGE: u32 = 932;
    // Your current ANSI code page (if 932 does not work)
    // const CODE_PAGE: u32 = CP_ACP;

    // If the arithmetic overflow occurs, this pattern is truncated to the first 3 bytes
    // because the final buffer size is 0x1_0000_0003.
    // Note 1:
    // \x83\x5c in Shift_JIS (code page 932) is Katakana SO (U+30BD).
    // Note 2:
    // corrupt \x83 in Shift_JIS (code page 932) is confirmed to be converted to
    // a middle dot (U+30FB), in this case it means a corrupted byte.
    let repeating_pattern: &[u8; 7] = b"AB\x83\x5cCDE";

    // Single repeating pattern
    let buffer_repeating_pattern: Vec<u16>;
    unsafe {
        let len = MultiByteToWideChar(CODE_PAGE, MULTI_BYTE_TO_WIDE_CHAR_FLAGS(0), repeating_pattern.as_slice(), None);
        if len == 0 {
            eprintln!("MultiByteToWideChar 1:1 error: {:?}", GetLastError());
            std::process::exit(1);
        }
        let mut tmp_buffer: Vec<u16> = core::iter::repeat(0u16).take(usize::try_from(len).unwrap()).collect();
        let len = MultiByteToWideChar(CODE_PAGE, MULTI_BYTE_TO_WIDE_CHAR_FLAGS(0), repeating_pattern.as_slice(), Some(tmp_buffer.as_mut_slice()));
        if len == 0 {
            eprintln!("MultiByteToWideChar 1:2 error: {:?}", GetLastError());
            std::process::exit(1);
        }
        buffer_repeating_pattern = tmp_buffer;
    }

    // Repeated until the buffer is larger than 4GiB.
    let mut buffer = Vec::<u8>::new();
    while buffer.len() < 0x1_0000_0000 {
        buffer.extend_from_slice(repeating_pattern);
    }
    let buffer_overflow_repeated: Vec<u16>;
    unsafe {
        // FAILURE EXPECTED HERE BECAUSE OF AN ARITHMETIC OVERFLOW CALLING THE WIN32 API.
        let len = MultiByteToWideChar(CODE_PAGE, MULTI_BYTE_TO_WIDE_CHAR_FLAGS(0), buffer.as_slice(), None);
        if len == 0 {
            eprintln!("MultiByteToWideChar 2:1 error: {:?}", GetLastError());
            std::process::exit(1);
        }
        let mut tmp_buffer: Vec<u16> = core::iter::repeat(0u16).take(usize::try_from(len).unwrap()).collect();
        let len = MultiByteToWideChar(CODE_PAGE, MULTI_BYTE_TO_WIDE_CHAR_FLAGS(0), buffer.as_slice(), Some(tmp_buffer.as_mut_slice()));
        if len == 0 {
            eprintln!("MultiByteToWideChar 2:2 error: {:?}", GetLastError());
            std::process::exit(1);
        }
        buffer_overflow_repeated = tmp_buffer;
    }

    let s_repeating_pattern = String::from_utf16_lossy(&buffer_repeating_pattern.as_slice());
    let s_overflow_repeated = String::from_utf16_lossy(&buffer_overflow_repeated.as_slice());

    println!("Repeating pattern:\n\t{s_repeating_pattern:?}");
    println!("Overflowing pattern (repeated the first pattern until the string exceeds 4GiB):\n\t{s_overflow_repeated:?}");
}

Additional Keywords: (potential) security, integer overflow

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions