-
Notifications
You must be signed in to change notification settings - Fork 13k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Auto merge of #36264 - matklad:zeroing-cstring, r=alexcrichton
Zero first byte of CString on drop Hi! This is one more attempt to ameliorate `CString::new("...").unwrap().as_ptr()` problem (related RFC: rust-lang/rfcs#1642). One of the biggest problems with this code is that it may actually work in practice, so the idea of this PR is to proactively break such invalid code. Looks like writing a `null` byte at the start of the CString should do the trick, and I think is an affordable cost: zeroing a single byte in `Drop` should be cheap enough compared to actual memory deallocation which would follow. I would actually prefer to do something like ```Rust impl Drop for CString { fn drop(&mut self) { let pattern = b"CTHULHU FHTAGN "; let bytes = self.inner[..self.inner.len() - 1]; for (d, s) in bytes.iter_mut().zip(pattern.iter().cycle()) { *d = *s; } } } ``` because Cthulhu error should be much easier to google, but unfortunately this would be too expensive in release builds, and we can't implement things `cfg(debug_assertions)` conditionally in stdlib. Not sure if the whole idea or my implementation (I've used ~~`transmute`~~ `mem::unitialized` to workaround move out of Drop thing) makes sense :)
- Loading branch information
Showing
2 changed files
with
74 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||
|
||
// ignore-emscripten | ||
|
||
// 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}; | ||
|
||
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()); | ||
} | ||
|
||
fn is_hello(s: *const c_char) -> bool { | ||
// `s` is a dangling pointer and reading it is technically | ||
// 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() | ||
} |