Skip to content
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

Fully describe safety requirements #561

Merged
merged 1 commit into from
Feb 6, 2023

Conversation

tcharding
Copy link
Member

Currently we have a wildcard on safety requirements, saying more or less "plus a bunch of other stuff we don't mention". This is not helpful.

Attempt to fully describe the safety requirements of creating a context from a raw context (all, signing only, and verification only).

Fix: #544

Note

This is best effort only, will require some thought to review. To do this I read https://doc.rust-lang.org/reference/behavior-considered-undefined.html and then I flicked through depend/secp256k1/src/secp256k1.c and util.h to look for things that could cause things in the linked to list of UB.

/// * The capabilities (`All`/`SignOnly`/`VerifyOnly`) of the context *must* match the flags
/// passed to libsecp256k1 when generating the context.
/// * The user must handle the freeing of the context (using the correct functions) by himself.
/// * `raw_ctx` must point to writable memory (cannot be `ffi::secp256k1_context_no_precomp`).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lifetime of raw_ctx pointer must outlive 'buf.

Non-safety warning: the returned value will not deallocate the pointer, nor destroy the context which may lead to memory leaks. The caller is responsible for these. Note that ManuallyDrop::drop (or drop_in_place) will only destroy the context, it will not free memory behind the pointer.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought the second point said that, I'll improve it to clarify. Thanks

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does but claiming it's safety is wrong and it might not be clear how to drop it properly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 382206f:

nit: I'd add the words "in particular," before "cannot be", to clarify that any non-writeable memory is a problem, not just the static context.

src/context.rs Outdated
/// * The capabilities (`All`/`SignOnly`/`VerifyOnly`) of the context *must* match the flags
/// passed to libsecp256k1 when generating the context.
/// * The user must handle the freeing of the context (using the correct functions) by himself.
/// * `raw_ctx` must point to writable memory (cannot be `ffi::secp256k1_context_no_precomp`).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lifetime of raw_ctx pointer must outlive 'buf.

Non-safety warning: the returned value will not deallocate the pointer, nor destroy the context which may lead to memory leaks. The caller is responsible for these. Note that ManuallyDrop::drop (or drop_in_place) will only destroy the context, it will not free memory behind the pointer.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you trolling me with your cutnpasta? :) (I'm joking, thanks for making sure I don't only fix one instance of the docs)

src/context.rs Outdated
/// * The capabilities (`All`/`SignOnly`/`VerifyOnly`) of the context *must* match the flags
/// passed to libsecp256k1 when generating the context.
/// * The user must handle the freeing of the context (using the correct functions) by himself.
/// * `raw_ctx` must point to writable memory (cannot be `ffi::secp256k1_context_no_precomp`).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lifetime of raw_ctx pointer must outlive 'buf.

Non-safety warning: the returned value will not deallocate the pointer, nor destroy the context which may lead to memory leaks. The caller is responsible for these. Note that ManuallyDrop::drop (or drop_in_place) will only destroy the context, it will not free memory behind the pointer.

src/context.rs Outdated
///
/// This is highly unsafe due to the number of conditions that aren't checked, specifically:
///
/// * `raw_ctx` needs to be a valid Secp256k1 context pointer that was generated by *exactly*
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pointer being generated is kinda vague. Maybe something like this:

raw_ctx must be a valid pointer (live, aligned...) to the memory that was initialized by secp256k1_context_preallocated_create (either called directly, or from this library by one of the context creation methods - all of them call it internally)

One issue with this is this promises we always call that function somehow and there won't be alternative initialization methods that are incompatible with this. I think this is reasonable assumption, right @apoelstra ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, that seems like a reasonable assumption for the indefinite future.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, thanks.

src/context.rs Outdated
///
/// * `raw_ctx` needs to be a valid Secp256k1 context pointer that was generated by *exactly*
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same pointer comment here

src/context.rs Outdated
/// * `raw_ctx` needs to be a valid Secp256k1 context pointer that was generated by *exactly*
/// the same code/version of the libsecp256k1 used here.
/// * The capabilities (`All`/`SignOnly`/`VerifyOnly`) of the context *must* match the flags
/// passed to libsecp256k1 when generating the context.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was written before we started fetching size from context during drop so it mandated exact flags. I think today it should be fine to require that at least the signing flag was supplied.

However to my knowledge secp256k1 asserts validity so it shouldn't lead to UB anyway? OTOH keeping it UB allows us more flexibility. But then wee should explain it's UB for flexibility and "no, we didn't document this incorrectly".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also think that, to the extent we can get away with it, we should avoid mentioning flags since they are going to go away with the next major release. (Since upstream merged bitcoin-core/secp256k1#1126 there are basically no more flags.)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that the size of context will stay compile-time-known forever? And having a custom mutable context is only needed for signing due to randomization?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both true, yes :).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool! So in that case we could drop AlignedType and make ffi::Context to have the size and alignment same as in C using the same trick I made PR with (just twice). That'd simplify many things.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the whole bullet point, commenting here to double check we are happy with that?

src/context.rs Outdated
///
/// * `raw_ctx` needs to be a valid Secp256k1 context pointer that was generated by *exactly*
/// the same code/version of the libsecp256k1 used here.
/// * The capabilities (`All`/`SignOnly`/`VerifyOnly`) of the context *must* match the flags
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only verification may be required - same as above.

@Kixunil
Copy link
Collaborator

Kixunil commented Dec 8, 2022

Also the other part of the issue is we don't provide any guarantees or restriction with regards to pointer we return. E.g. one footgun which I only noticed recently is that the context must not be moved - it's effectively Pin<SomeRef<ffi::ExternContext>> (which would be the proper type that would allow us to get rid of memory management but sadly, extern types are unstable).

This is not visibly documented in the Rust library and given how common moves are in Rust it may be even bigger footgun than in C.

@apoelstra
Copy link
Member

I don't understand why contexts can't be moved. They don't have any self-referential pointers even in the C code.

@Kixunil
Copy link
Collaborator

Kixunil commented Dec 8, 2022

They don't but the last time I checked the docs moving is forbidden so I assume they may in the future.

@apoelstra
Copy link
Member

They don't but the last time I checked the docs moving is forbidden so I assume they may in the future.

Huh, TIL. I don't know why this warning is there -- presumably to allow for future iterations to use self-referential pointers, I suppose -- but despite the warning, it is safe (with all current versions of the C library) to do moves or copies. The warning also claims that we can't read from the memory, a requirement that makes no sense and can't be enforced in C or Rust or anywhere. So we may want to PR upstream to clarify it. The warning was added in bitcoin-core/secp256k1#566 which has a ton of discussion, including by me and Elichai.

If Pin were reasonably ergonomic to use, I'd agree with you, but because it's such a heavy cognitive burden and also causes ergonomic problems, I'd rather not. Even if we could make it work across the FFI boundary on stable Rust.

@Kixunil
Copy link
Collaborator

Kixunil commented Dec 8, 2022

Oh crap, I read that wrong, it's concerning prealloc pointer not the returned one. So it's pretty much Rust borrow rules.

Maybe it's time to lift the restriction to resemble stacked borrows instead? :)

heavy cognitive burden and also causes ergonomic problems

This only concerns people working with FFI. It's a non-issue for anyone else. Even if we wanted to make bare context using Rust Box/other structures we would just have methods taking self: Pin<&mut self> which are no less ergonomic.

But still, I'd rather not to either.

@tcharding
Copy link
Member Author

Thanks for the reviews. Will re-spin when I can give this my full concentration.

@tcharding
Copy link
Member Author

Please re-review carefully. Note also that I've removed the duplication of the docs and instead referred folks to the docs on Secp256k1::from_raw_all to reduce the maintenance burden.

src/context.rs Outdated
@@ -351,15 +351,22 @@ impl<'buf> Secp256k1<AllPreallocated<'buf>> {

/// Creates a context from a raw context.
///
/// The returned [`core::mem::ManuallyDrop`] context will never deallocate the memory pointed to
/// by `raw_ctx`, nor destroy the context, this may lead to memory leaks. `ManuallyDrop::drop`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 382206f:

nit: this may lead to memory leaks should be its own sentence.

src/context.rs Outdated
@@ -351,15 +351,22 @@ impl<'buf> Secp256k1<AllPreallocated<'buf>> {

/// Creates a context from a raw context.
///
/// The returned [`core::mem::ManuallyDrop`] context will never deallocate the memory pointed to
/// by `raw_ctx`, nor destroy the context, this may lead to memory leaks. `ManuallyDrop::drop`
/// (or [`core::ptr::drop_in_place`]) will only destroy the context, the caller is required to
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 382206f:

Similarly the caller is required... should be its own sentence, or we should use a semicolon rather than comma

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used a semicolon (because it makes me feel like an English grammar guru). We now have:

    /// The returned [`core::mem::ManuallyDrop`] context will never deallocate the memory pointed to
    /// by `raw_ctx` nor destroy the context. This may lead to memory leaks. `ManuallyDrop::drop`
    /// (or [`core::ptr::drop_in_place`]) will only destroy the context; the caller is required to
    /// free the memory.

@tcharding
Copy link
Member Author

Force pushed changes to the docs as suggested (and posted above).

apoelstra
apoelstra previously approved these changes Dec 13, 2022
Copy link
Member

@apoelstra apoelstra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 0bd9c96

@apoelstra
Copy link
Member

Will wait on @Kixunil before merging this.

Copy link
Collaborator

@Kixunil Kixunil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My brain is working at around 30% capacity now so can't say for sure but I don't see anything that's obviously wrong.

///
/// This is highly unsafe due to a number of conditions that aren't checked, specifically:
///
/// * `raw_ctx` must be a valid pointer (live, aligned...) to memory that was initialized by
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should mention AlignedType as the one providing the correct alignment information.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just realised I never added anything on AlignedType as you suggest here.

src/context.rs Outdated
/// `secp256k1_context_preallocated_create` (either called directly or from this library by
/// one of the context creation methods - all of which call it internally).
/// * The version of `libsecp256k1` used to create `raw_ctx` must be the same as the version
/// used by this library.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it about version (leading people to think "version number") or should it be the exact same binary? Maybe compiling with different flags could break it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be the exact same binary ... I agree that "same version" understates the requirement here. But given that there are no "version numbers" for libsecp, at least not until yesterday, I don't think there's any practical way to interpret this dangerously.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure I've seen version numbers whether they are "real" doesn't matter that much since people will misinterpret it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, let's change this text to clarify that the version used to create raw_ctx must be exactly the one linked into this library.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used as suggested and force pushed, no other changes.

@tcharding
Copy link
Member Author

Will need to be rebased on #565

apoelstra
apoelstra previously approved these changes Dec 21, 2022
Copy link
Member

@apoelstra apoelstra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK da49368

@tcharding
Copy link
Member Author

Rebased to see if CI is working. No other changes.

@tcharding tcharding mentioned this pull request Jan 30, 2023
@apoelstra
Copy link
Member

We are getting unrelated 500 errors from a docker server now.

@Kixunil
Copy link
Collaborator

Kixunil commented Jan 31, 2023

Try re-running the failed jobs, it did work in bitcoin crate.

Currently we have a wildcard on safety requirements, saying more or less
"plus a bunch of other stuff we don't mention". This is not helpful.

Attempt to fully describe the safety requirements of creating a context
from a raw context (all, signing only, and verification only).

Fix: rust-bitcoin#544
@tcharding
Copy link
Member Author

Force push a new commit hash just to trigger CI, no changes to the PR.

1 similar comment
@tcharding
Copy link
Member Author

Force push a new commit hash just to trigger CI, no changes to the PR.

Copy link
Member

@apoelstra apoelstra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK e705bcf

@apoelstra
Copy link
Member

@Kixunil I'd like your ACK on this before merging it.

/// * `raw_ctx` needs to be a valid Secp256k1 context pointer.
/// that was generated by *exactly* the same code/version of the libsecp256k1 used here.
/// * The capabilities (All/SignOnly/VerifyOnly) of the context *must* match the flags passed to libsecp256k1
/// when generating the context.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one still applies but is not mentioned in the new text.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't remember why I removed this exactly because it was a long time ago but it seems such an obvious omission that I think me from the past would of had a reason. I'm out of my depth of knowledge here but I thought there was something about the libsecp context not differentiating any more? Are you sure we still want this line?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From looking at gen_new I don't see that the libsecp context created is any different for the different All/SignOnly/VerifyOnly tags, or am I missing something?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah, there is now no difference upstream between the capabilities, they're total no-ops. But we haven't updated the Rust API to reflect this yet because it'd be a big breaking change and we wanted to make sure upstream was finished changing first.

So I think this text is ok to drop.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sweet, my brain worked!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need to?

No. Upstream had an actual release :).

Maybe we could start with using All as the default type parameter and change the generic functions to accept any type parameter and deprecate the All, SignOnly and VerifyOnly types. This is not breaking but reflects it in the API.

Another strategy might be to stop exposing context objects in the API at all.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still need them for re-randomization, don't we?

Anyway, I prefer the API to be modified a little to not being modified at all because of the amount of work.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In #388 (or somewhere) we came to a solution that enables re-randomization without needing explicit context objects.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another strategy might be to stop exposing context objects in the API at all.

Would we use a global singleton for the context and leave the secp256k1-sys API the same?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tcharding yes, the secp-sys API would remain the same.

Copy link
Collaborator

@Kixunil Kixunil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK e705bcf

Assuming dropping of flags gets documented in the future PR.

@apoelstra apoelstra merged commit 6ec968a into rust-bitcoin:master Feb 6, 2023
@tcharding tcharding deleted the 12-08-safety-docs branch March 15, 2023 02:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Document **all** safety requirements or remove undocumented unsafe functions from public API
3 participants