Can mprotect succeed in Rust?

I'm curious if mprotect can be used successfully in Rust. It'll basically set memory permissions as far as the address range provided to it is aligned to memory page boundaries. I did some testing to see if it can work in Rust:

let y: i32 = 10;
let addr = &y as *const _;
let mut aligned = (addr as i32) & !(pagesize-1); //pagesize == 4096
//mprotect(addr: *mut c_void, len: size_t, prot: c_int) -> c_int
let r = mprotect(aligned as *mut c_void, pagesize as usize, 1);     

This code will fail with errno set to 12 (Cannot allocate memory).
Is there a way to make this work? Or perhaps is there any other way to set memory page permissions in Rust?

1 Like

There's nothing about Rust that would make it fail.

Are you on a 32-bit platform? If not, you shouldn't be casting pointers to i32, though I think Rust would forbid that anyway (can't check from my phone) (edit: it doesn't forbid it). In any case, better to use usize.

Other than that I'm not sure why it wouldn't work, though I hope you realize that if it does work, the result will be a crash (since you're making some random portion of the stack read-only). I suggest checking your operating system's 'man mprotect' to see what can cause it to fail with ENOMEM.

Thanks. mprotect normally works on my system on a C program. I thought maybe there's something about Rust memory layout that I'm missing.
But why would making the stack read only result in a crash if it only contains my data but no return address etc..?

mprotect docs say

       int mprotect(void *addr, size_t len, int prot);

DESCRIPTION
       mprotect()  changes protection for the calling process's memory page(s) contain‐
       ing any part of the address range in the interval [addr, addr+len-1].  addr must
       be aligned to a page boundary.

Your example program does nothing to ensure that the addr passed is aligned to page boundary. You'd use a custom allocator or mmap to ensure this.

Well I think my example program does try to ensure that addr is aligned to the page boundary. I don't think a custom allocator is needed if the alignment is done properly. Perhaps my program's alignment calculation isn't correct.

As @comex asked, are you on a 32-bit or 64-bit system? On a 64-bit system, (addr as i32) will lose any high bits, probably making the address totally invalid -> ENOMEM. I'm guessing your pagesize is also i32, which means !(pagesize-1) will be 0xFFFFF000, which may also mask high bits (but could be OK if you did a sign extended cast up).

It's best to just work with usize for the whole calculation, as that will match the true pointer size.

Why do you think the stack only contains your data?

Even the call to mprotect itself writes a return address on the stack, and much of the generated code will probably try to read and write data on the stack. Just because your y is semantically const does not mean the compiler won't do anything with that memory location, and certainly the rest of the page around it is fair game.

I don't think a custom allocator is needed if the alignment is done properly.

In fact, custom allocator is the simplest and safest way. see https://github.com/quininer/memsec/blob/master/src/alloc.rs#L49 https://github.com/quininer/memsec/blob/master/src/alloc.rs#L233

1 Like

Yes, in fact I had to use a 64-bit variable to hold the address.

mprotect does work in my code with a 64-bit representation of the address. I missed that out. However, this alloc_aligned seems to be interesting too. Thanks.