Skip to content

Instantly share code, notes, and snippets.

@rexim
Last active October 31, 2024 20:32
Show Gist options
  • Save rexim/38c176fe4669ef83db69aca9909d7b7f to your computer and use it in GitHub Desktop.
Save rexim/38c176fe4669ef83db69aca9909d7b7f to your computer and use it in GitHub Desktop.
The Most Memory Safe Buffer Overflow in Rust!
// The Most Memory Safe Buffer Overflow in Rust!
//
// Consider all the code below under Public Domain
//
// How to build:
// $ rustc main.rs
//
// Wrong password:
// $ printf "hello\n" | ./main
//
// Right password:
// $ printf "password\n" | ./main
//
// Universal password:
// $ printf "aaaaaaaaaaaaaa\0aaaaaaaaaaaaaa\0" | ./main
//
// Support Rust Recovery Foundation: https://rustrecoveryfoundation.neocities.org/
use std::io::{BufRead, Write};
const BUF_CAP: usize = 15;
type Ptr = usize;
fn alloc_buffer(mem: &mut Vec::<char>, size: usize) -> Ptr {
let result = mem.len();
for _ in 0..size {
mem.push(' ')
}
result
}
fn alloc_str(mem: &mut Vec<char>, s: &str) -> Ptr {
let result = mem.len();
for c in s.chars() {
mem.push(c)
}
mem.push('\0');
result
}
fn read_line_into_buffer(input: &mut impl BufRead, mem: &mut Vec<char>, buf: Ptr) {
let mut s = String::new();
let n = input.read_line(&mut s).unwrap();
for (i, c) in s.chars().enumerate() {
mem[buf + i] = c;
}
if mem[buf + n - 1] == '\n' {
mem[buf + n - 1] = '\0'
} else {
mem[buf + n] = '\0';
}
}
fn streq(mem: &Vec<char>, mut s1: Ptr, mut s2: Ptr) -> bool {
while mem[s1] != '\0' && mem[s2] != '\0' {
if mem[s1] != mem[s2] {
return false;
}
s1 += 1;
s2 += 1;
}
mem[s1] == '\0' && mem[s2] == '\0'
}
fn main() {
let mut mem = Vec::<char>::new();
let buffer = alloc_buffer(&mut mem, BUF_CAP);
let password = alloc_str(&mut mem, "password");
alloc_buffer(&mut mem, BUF_CAP);
print!("Password: ");
std::io::stdout().flush().unwrap();
read_line_into_buffer(&mut std::io::stdin().lock(), &mut mem, buffer);
if streq(&mem, buffer, password) {
println!("Access Granted!")
} else {
println!("Access Denied!")
}
}
@rexim
Copy link
Author

rexim commented Feb 19, 2022

People on the Internet say that this does not prove that Rust is memory unsafe. I have no idea why they think that this is to prove unsafety of Rust. I explicitly said that this is The Most Memory Safe Buffer Overflow in Rust. Read the description carefully.

@ElectronicRU
Copy link

found it from the mention in your brainfuck video, excellent shitpost

@lovelymono
Copy link

lovelymono commented May 21, 2022

It's not really buffer overflow though, is it? That's just an ordinary buffer reuse, you can do that in any language >.>

@ElectronicRU
Copy link

It's not really buffer overflow though, is it? That's just an ordinary buffer reuse, you can do that in any language >.>

It's an overflow of a logically allocated sub-buffer. It leads to the same kinds of bugs and vulnerabilities.
Of course, it's kinda like doing things the hard wrong way on purpose, but it goes to show that Rust merely makes it harder to have buffer overflows, but it can't stop you if you're brave enough.

@marcelodarroyo
Copy link

It's not really a buffer overflow. It just overwrite a 'logical' aliased variable as any other logical error. It doesn't enable attacks like return address overwriting.

@willswordpath
Copy link

willswordpath commented Mar 22, 2023

In summary, it can only corrupt the data in the same Vector (in practice you'll never split a Vector's storage into two halves delibrately for "Raw Ptr" uses as in this example, you definitely will put the data receiving buffer into a standalone Vector), any tries to write to the range beyond this Vector will cause out-of-bound panic of Rust, and no need to mention the heap layout is mostly randomized, its impossible to affect adjacent data structures on heap, so this vulnerability is extremely hard to be exploited.
This example merely shows how hard deliberately implementing a buffer overflow in Rust is.

@grepwood
Copy link

grepwood commented Apr 9, 2023

60 SLOC is hardly difficult for "deliberately" doing anything.

@evertonse
Copy link

It's not really a buffer overflow. It just overwrite a 'logical' aliased variable as any other logical error. It doesn't enable attacks like return address overwriting.

Actually it does, but you gotta really try, see https://boulderbugle.com/overwritting-return-address-in-rust-you-8s92wy28

@rexim
Copy link
Author

rexim commented Oct 20, 2023

@j-hc because they are and no amount of mockery will change this fact.

@psprojectC
Copy link

@rexim istg is that j-hc guy an idiot or something? how can u not know indices and pointers are literally the same thing? like has he never declared an index to be restrict?? has he never dereferenced an index?? hes probably never even heard of index arithmetic 😤😤
istg the state of rust fanboys denying basic reality is laughable 😂😂😂💯💯

@rexim
Copy link
Author

rexim commented Nov 9, 2023

@psprojectC I personally wouldn't say they are "literally the same thing". Rather they are similar in some of the problems they introduce, but if you prefer to believe what you've just said, sure. That's your choice.

@psprojectC
Copy link

@rexim theyre really not. everyone here knows an index doesnt exist without a pointer. which makes it even stranger why when ure sarcastically told "indices are just lyke pointers" u actually deny it

@rexim
Copy link
Author

rexim commented Nov 9, 2023

@psprojectC They are just lyke pointers, but they are not literally the same thing. Did the gist trigger you to the point where you feel like you need to argue over the definitions of such basic words? (:

@psprojectC
Copy link

@rexim what thing from this gist would i be triggered over? im not sure i understand, could u please explain?

@TinusgragLin
Copy link

I see there are two problems, but for one of them, instead of 'buffer overflow', I think it's more like not having a length field in your LinkedList class whose get_length is supposed to be O(1).
Here is my summary of what might go wrong in this example:

let n = // some number not known in compile time;

// We define `buf_a` to be the first 3 bytes of `v`. Other bytes are considered something else.
let v: Vec<u8> = vec![0, 0, 0, 20, 20, 20];
// No! This is not actually an element of `buf_a`, we just accessed something outside of `buf_a`!
let element_of_buf_a = v[3];
// No! We might get a index out of bound error!
let element_of_v = v[n];

// But the compiler is fine with this code and does not even warn us!

@rexim Do you think this minifies this example? I am new to Rust, so I might be wrong.

@TinusgragLin
Copy link

My roommate coming from C thought list[index] might actually cause SegFault. So for anyone not familiar with Rust, list[index] is simply list.get(index).unwrap(), which basically is:

if index < list.length() {
    return list.get_without_check(index); // the real get_without_check is actually marked unsafe. 
} else {
    WTFYouAreDoingYourIndexIsCriminalYouAssHole();
}

@rexim
Copy link
Author

rexim commented Nov 10, 2023

I see there are two problems, but for one of them, instead of 'buffer overflow', I think it's more like not having a length field in your LinkedList class whose get_length is supposed to be O(1). Here is my summary of what might go wrong in this example:

let n = // some number not known in compile time;

// We define `buf_a` to be the first 3 bytes of `v`. Other bytes are considered something else.
let v: Vec<u8> = vec![0, 0, 0, 20, 20, 20];
// No! This is not actually an element of `buf_a`, we just accessed something outside of `buf_a`!
let element_of_buf_a = v[3];
// No! We might get a index out of bound error!
let element_of_v = v[n];

// But the compiler is fine with this code and does not even warn us!

@rexim Do you think this minifies this example? I am new to Rust, so I might be wrong.

Indices in Rust specifically don't have a concept of ownership attached to them with all the corresponding consequences which you should always keep in mind.

@rexim
Copy link
Author

rexim commented Nov 10, 2023

My roommate coming from C thought list[index] might actually cause SegFault. So for anyone not familiar with Rust, list[index] is simply list.get(index).unwrap(), which basically is:

if index < list.length() {
    return list.get_without_check(index); // the real get_without_check is actually marked unsafe. 
} else {
    WTFYouAreDoingYourIndexIsCriminalYouAssHole();
}

Here is the thing about C... Depending on the situation list[index] may NOT segfault even when index >= list.length(), which is way worse ;)

@TinusgragLin
Copy link

I see there are two problems, but for one of them, instead of 'buffer overflow', I think it's more like not having a length field in your LinkedList class whose get_length is supposed to be O(1). Here is my summary of what might go wrong in this example:

let n = // some number not known in compile time;

// We define `buf_a` to be the first 3 bytes of `v`. Other bytes are considered something else.
let v: Vec<u8> = vec![0, 0, 0, 20, 20, 20];
// No! This is not actually an element of `buf_a`, we just accessed something outside of `buf_a`!
let element_of_buf_a = v[3];
// No! We might get a index out of bound error!
let element_of_v = v[n];

// But the compiler is fine with this code and does not even warn us!

@rexim Do you think this minifies this example? I am new to Rust, so I might be wrong.

Indices in Rust specifically don't have a concept of ownership attached to them with all the corresponding consequences which you should always keep in mind.

Hmm... I am not sure what you mean, are you saying that you can not move an element out of Vec if it is not Copy and semantically vec[index] actually mean copy the element out? Or that the traits backing the [] syntax(i.e. Index IndexMut) only gives &T and &mut T but not T? Or something about index type itself (usize, Range, ...)?

I will be very grateful if you can elaborate since I am still learning Rust.

@ElectronicRU
Copy link

@TinusgragLin yes, Index and IndexMut do in fact provide &T and &mut T.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment