Skip to content

UB free test for CString Drop #36607

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/libstd/ffi/c_str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ impl CString {
#[stable(feature = "cstring_drop", since = "1.13.0")]
impl Drop for CString {
fn drop(&mut self) {
unsafe { *self.inner.get_unchecked_mut(0) = 0; }
unsafe { ptr::write_volatile(self.inner.as_mut_ptr(), 0u8) }
}
}

Expand Down
49 changes: 49 additions & 0 deletions src/test/run-pass/auxiliary/allocator-leaking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// no-prefer-dynamic

#![feature(allocator, core_intrinsics, libc)]
#![allocator]
#![crate_type = "rlib"]
#![no_std]

extern crate libc;

#[no_mangle]
pub extern fn __rust_allocate(size: usize, align: usize) -> *mut u8 {
unsafe {
libc::malloc(size as libc::size_t) as *mut u8
}
}

#[no_mangle]
pub extern fn __rust_deallocate(ptr: *mut u8, old_size: usize, align: usize) {
// Do nothing at all.
}

#[no_mangle]
pub extern fn __rust_reallocate(ptr: *mut u8, old_size: usize, size: usize,
align: usize) -> *mut u8 {
unsafe {
libc::realloc(ptr as *mut _, size as libc::size_t) as *mut u8
}
}

#[no_mangle]
pub extern fn __rust_reallocate_inplace(ptr: *mut u8, old_size: usize,
size: usize, align: usize) -> usize {
unsafe { core::intrinsics::abort() }
}

#[no_mangle]
pub extern fn __rust_usable_size(size: usize, align: usize) -> usize {
unsafe { core::intrinsics::abort() }
}
43 changes: 13 additions & 30 deletions src/test/run-pass/cstring-drop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,42 +8,25 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// ignore-emscripten
// no-prefer-dynamic
// aux-build:allocator-leaking.rs

// Test that `CString::new("hello").unwrap().as_ptr()` pattern
// leads to failure.

use std::env;
use std::ffi::{CString, CStr};
use std::os::raw::c_char;
use std::process::{Command, Stdio};
extern crate allocator_leaking;

fn main() {
let args: Vec<String> = env::args().collect();
if args.len() > 1 && args[1] == "child" {
// Repeat several times to be more confident that
// it is `Drop` for `CString` that does the cleanup,
// and not just some lucky UB.
let xs = vec![CString::new("Hello").unwrap(); 10];
let ys = xs.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
drop(xs);
assert!(ys.into_iter().any(is_hello));
return;
}

let output = Command::new(&args[0]).arg("child").output().unwrap();
assert!(!output.status.success());
}
use std::ffi::CString;
use std::ptr;

fn is_hello(s: *const c_char) -> bool {
// `s` is a dangling pointer and reading it is technically
fn main() {
let ptr = CString::new("Hello").unwrap().as_ptr();
// `ptr` is a dangling pointer and reading it is almost always
// undefined behavior. But we want to prevent the most diabolical
// kind of UB (apart from nasal demons): reading a value that was
// previously written.
//
// Segfaulting or reading an empty string is Ok,
// reading "Hello" is bad.
let s = unsafe { CStr::from_ptr(s) };
let hello = CString::new("Hello").unwrap();
s == hello.as_ref()
// previously written. So we make sure that CString zeros the
// first byte in the `Drop`.
// To make the test itself UB-free we use a custom allocator
// which always leaks memory.
assert_eq!(unsafe { ptr::read(ptr as *const [u8; 6]) } , *b"\0ello\0");
}