Skip to content

Commit f4e370b

Browse files
authored
Check input in rustls_error to avoid UB (#195)
The input parameter for rustls_error was `rustls_result`. However, in Rust it's undefined behavior for an enum to hold an invalid value. That meant that if C passed an invalid value to rustls_error, UB would result. This changes the input parameter to be a uint, and relies on a macro from the num_enum crate to check the value of that input parameter. If the input is invalid, we emit the error for "InvalidParameter".
1 parent 42a1557 commit f4e370b

File tree

4 files changed

+108
-83
lines changed

4 files changed

+108
-83
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ If you are importing this as a library from other Rust code, you should import `
4343
expected.
4444
- `rustls_version` returns a `rustls_str` that points to a static string in
4545
memory, and the function no longer accepts a character buffer or length.
46+
- `rustls_error` now takes a `unsigned int` instead of rustls_result directly.
47+
This is necessary to avoid undefined behavior if an invalid enum value is
48+
passed.
4649
- Some errors starting with RUSTLS_RESULT_CERT_ have been removed, and
4750
some renamed.
4851
- rustls_client_config_builder_set_protocols is now rustls_client_config_builder_set_alpn_protocols.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ libc = "0.2"
1414
sct = "0.7"
1515
rustls-pemfile = "0.2.1"
1616
log = "0.4.14"
17+
num_enum = "0.5.4"
1718

1819
[dev_dependencies]
1920
cbindgen = "*"

src/error.rs

+24-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
use std::{cmp::min, fmt::Display, slice};
1+
use std::{cmp::min, convert::TryFrom, fmt::Display, slice};
22

33
use crate::ffi_panic_boundary;
4-
use libc::{c_char, size_t};
4+
use libc::{c_char, c_uint, size_t};
5+
use num_enum::TryFromPrimitive;
56
use rustls::Error;
67

78
/// A return value for a function that may return either success (0) or a
@@ -18,7 +19,7 @@ impl rustls_result {
1819
/// UTF-8 encoded, and not NUL-terminated.
1920
#[no_mangle]
2021
pub extern "C" fn rustls_error(
21-
result: rustls_result,
22+
result: c_uint,
2223
buf: *mut c_char,
2324
len: size_t,
2425
out_n: *mut size_t,
@@ -35,6 +36,7 @@ impl rustls_result {
3536
}
3637
slice::from_raw_parts_mut(buf as *mut u8, len as usize)
3738
};
39+
let result: rustls_result = rustls_result::try_from(result).unwrap_or(rustls_result::InvalidParameter);
3840
let error_str = result.to_string();
3941
let len: usize = min(write_buf.len() - 1, error_str.len());
4042
write_buf[..len].copy_from_slice(&error_str.as_bytes()[..len]);
@@ -60,8 +62,26 @@ impl rustls_result {
6062
}
6163
}
6264

65+
#[test]
66+
fn test_rustls_error() {
67+
let mut buf = [0 as c_char; 512];
68+
let mut n = 0;
69+
rustls_result::rustls_error(0, &mut buf as *mut _, buf.len(), &mut n);
70+
let output: String = String::from_utf8(buf[0..n].iter().map(|b| *b as u8).collect()).unwrap();
71+
assert_eq!(&output, "a parameter had an invalid value");
72+
73+
rustls_result::rustls_error(7000, &mut buf as *mut _, buf.len(), &mut n);
74+
let output: String = String::from_utf8(buf[0..n].iter().map(|b| *b as u8).collect()).unwrap();
75+
assert_eq!(&output, "OK");
76+
77+
rustls_result::rustls_error(7101, &mut buf as *mut _, buf.len(), &mut n);
78+
let output: String = String::from_utf8(buf[0..n].iter().map(|b| *b as u8).collect()).unwrap();
79+
assert_eq!(&output, "peer sent no certificates");
80+
}
81+
6382
#[allow(dead_code)]
64-
#[repr(C)]
83+
#[repr(u32)]
84+
#[derive(TryFromPrimitive)]
6585
pub enum rustls_result {
6686
Ok = 7000,
6787
Io = 7001,

0 commit comments

Comments
 (0)