-
Notifications
You must be signed in to change notification settings - Fork 297
Description
Behold, use-after-free!
use secp256k1::Secp256k1;
fn make_bad_secp() -> Secp256k1::<secp256k1::AllPreallocated<'static>> {
// in principle `Box` is not needed but if it's not here it won't show up as a crash.
let mut array = Box::new([secp256k1::ffi::types::AlignedType::ZERO; 1024]);
secp256k1::Secp256k1::<secp256k1::AllPreallocated<'static>>::preallocated_gen_new(&mut *array).unwrap()
}
fn main() {
let secp = make_bad_secp();
let secret = secp256k1::SecretKey::from_slice(b"release the nasal daemons!!!!!!!").unwrap();
let pubkey = secp256k1::PublicKey::from_secret_key(&secp, &secret);
println!("Dear compiler, do not optimize this computation {}", pubkey);
}
Yep, no unsafe
. Curiously, this seems to corrupt the heap on my machine or something because it crashes after printing the key. Anyway, it's still UB so I'm lucky I didn't get nasal daemons. 🤷♂️
But, but, but there's C: 'buf
bound!
The problem is C: 'buf
means C
outlives 'buf
, so in our case 'static 'buf
which is trivially true for all lifetimes 'buf
.
In theory the bound should've been inverted: 'buf: C
. But sadly, that's not a valid Rust syntax.
The bad news is I see no way of avoiding putting lifetimes everywhere with the current design.
Possibilities I can think of:
- Make
Secp256k1
unsized type (to preventmem::swap
which is curiously forbidden by secp). This will needlessly addsize
back as slice metadata but we can't do anything about it until extern types are stabilized and in our MSRV. I thinkBox<Secp256k1>
could be valid, if not we need to have our ownBoxedSecp256k1
. - Add a lifetime parameter to
Context
,Signing
, andVerification
traits contaminating everything. - GAT can probably help but I can't think of how from top off my head and this would unreasonably bump MSRV so I don't want to spending much time on thinking about this one.
Maybe there's some ridiculous hack I didn't think about (my mind is not fresh rn), so if anyone knows that'd be great.
@apoelstra found another problem with the API: it's possible to cause invalid deallocation. Demo for reference:
fn main() {
let mut array = [secp256k1::ffi::types::AlignedType::ZERO; 1024];
let secp = secp256k1::Secp256k1::<secp256k1::All>::preallocated_gen_new(&mut array).unwrap();
let secret = secp256k1::SecretKey::from_slice(b"release the nasal daemons!!!!!!!").unwrap();
let pubkey = secp256k1::PublicKey::from_secret_key(&secp, &secret);
println!("Dear compiler, do not optimize this computation {}", pubkey);
}
Notice that the array lives for the duration of the whole function so wrong lifetimes do not cause UB but invalid deallocation does.