-
Notifications
You must be signed in to change notification settings - Fork 265
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
Only build bundled symbols if the user wants to use them #380
Comments
I think we want this to be a separate cfg flag - users may want to use the built-in library with an external implementation, but, yes, we should support this. |
This flag exists for rust-secp257k1-zkp, right? Are there other users? I think for secp-zkp @str4d may be correct that we're better off just not building included libsecp at all. |
Do we want to support this though? I remember in the past we made some decisions here due to the fact that we control the exact version of libsecp EDIT: I'm not saying we shouldn't, just that if we do we need to make sure we don't have anything that assumes implementation-defined details of libsecp |
IIRC part of the reason for this was to be able to link |
We could remove it in a RC of the next major version rev, and see if it breaks rust-bitcoinconsensus or rust-secp-zkp. |
This is in fact almost precisely my new use case. |
Thanks :) ok we won't remove it. Let's give your "don't build libsecp" proposa a shot. I wonder why we didn't try using this for secp-zkp rather than doing the crazy symbol-renaming thing that we did do.. |
That fixes other problems too, let's say I'm using |
cc @jonasnick, you may know the answer here. |
IIRC the secp-zkp solution pre-dated the rust-bitcoinconsensus solution ... we had originally intended to use renaming for rust-bitcoinconsensus, but our initial renaming solution was really shitty and didn't work (and this was at the time when Tamas was maintaining rust-bitcoinconsensus but no longer really active). Then Matt came up with this no-symbol-export solution, and then independently Elichai un-shittified the renaming script. But my memory could be wrong on every one of those points :). |
Not that I am aware. In Here for example we pass a We did this so that |
Actually not 100% about this. We don't create any bindings for it so I guess it depends on LTO doing its job to figure out that this C code is unused. |
Unfortunately it is not unused. It is used by rust-secp, and we take context objects created by libsecp256k1 (through rust-secp) then pass them to libsecp256k1-zkp functions, which is questionably-defined behavior. If we could just avoid complinig and using libsecp256k1 in rust-secp-zkp that would be a huge improvement. |
I was referring to the copy of libsecp256k1zkp that is mostly unused. ( |
Ah, I remember why we do not use this in rust-secp-zkp:
Perhaps there are some launch-code-like tricks we could use to manually whitelist projects who are allowed to replace symbols ... but probably symbol renaming is easier than that. |
Hmmm, a malicious crate that gets pulled in as a dep can always create a build script that exports the relevant RUSTFLAGS environment variable, too, though - I've seen people demo this to use unstable features from "stable" crates. |
Would the inverse be reasonable? Have a default-enabled feature flag that performs the rename and builds/bundles the library, and then rely on downstream users to disable it as they need. It would make transitive disabling harder (intermediate projects would need to expose an equivalent default-enabled feature flag), but has a safer fallback (since cargo takes the union of enabled flags, so as long as one dependency on |
I think in practice this would usually break the build -- downsteam users can't unilaterally disable features, in case any of their dependencies enable it (and crate trying to, say, depend on rust-secp without the default features, would be forced to explicitly enable it). @TheBlueMatt Can a downstream crate actually change the RUSTFLAGS that other crates are built with? |
Intuitive I'd guess its race-y, but I have seen demos of crates that definitely change the environment enough to use unstable rust features with a stable compiler (ie via RUSTC_BOOTSTRAP). I assume if you happen to get run before the other crate in the build order you could somehow? In any case, on most platforms you can also register dlopen hooks and do other nasty stuff so I'm not sure how much of a concern this can really be? |
Yeah, okay, I'm starting to be convinced that we could make this a cargo feature, and that the resulting attack surface would be no worse than the existing "if downstream users depend on malicious crates they are in trouble" surface. |
Without knowing must about the specifics of rustc and cargo here, it seems reasonable to assume that a malicious crate has control over the entire build process, just because the build itself already can run arbitrary code. (I guess this is true for almost every non-trivial build system.) If I wanted to attack this crate specifically, I could even set the CC variable and attack it at the C level... |
I'm thinking of this problem as being analogous to no-std mode that is implemented additively via a But here it has the added advantage that builds wouldn't actually fail to compile in many cases - they would just use the The other argument I'd make is that a feature flag that adds bundled symbols makes sense given that feature flags are additive; a feature flag that removes them both doesn't fit the additive model, and leaves downstream crates open to "spooky action at a distance": a user depends on two libraries that depend on this one, one of which enables that flag and provides its own symbols, and then the other crate is unexpectedly using an unrelated sibling's symbols. If the crate providing its own symbols legitimately needs them and can't use the default vendored symbols at all, it seems reasonable to have that crate detect this situation and break the build rather than silently altering other crates' expected symbols. |
@str4d the build would fail to build if one dependency needed In any case, I am leaning toward putting an additive |
Ah, sorry for the confusion; I'd expected that this feature flag would retain the current renaming behavior (or rather, the opposite of the current behavior: enabling the flag would have this crate look for the renamed symbols instead of the originals). So in your first scenario above, two sets of symbols would be built, but only the renamed vendored ones would be linked. |
Maybe I'm missing something here but aren't you folks trying to reinvent the native cargo feature that can override build scripts? |
I think that for the cases where this is likely to be needed, the downstream user controls the whole dependency tree. So if one of their dependencies enabled the feature then that would be a bug they would be in a position to fix, e.g. by vendoring that dependency. Note that if |
Interesting @Kixunil ... this seems very close to what we want, but not quite there. Could we use this in rust-secp256k1-zkp to say "ignore the rust-secp256k1 build script, use ours instead" and have this do the right thing even if rust-secp and rust-secp-zkp are parallel dependencies of some third crate? |
@apoelstra this is only usable from binaries, so it can't be used out-of-the-box. Before I start to figure out some horrible hack I'd like to understand why there are even two versions of the library.
|
Because -zkp implements a lot of bleeding-edge crypto which is not appropriate for libsecp and is not useful for Bitcoin Core.
No.
I don't understand this question. We have two C libraries so we need two sets of FFI bindings.
We need to do this so that we can share context objects, which are megabytes in size and take many milliseconds to create. There is no risk as long as the libsecp functions have the same signatures in both libraries, which we (the maintainers of both libsecp and libsecp-zkp) can ensure, because (a) we are (mostly) the same people, (b) libsecp-zkp frequently merges in upstream changes from libsecp, (c) libsecp-zkp never messes with the upstream functionality. I wouldn't characterize this as "merging libraries". The idea is that libsecp256k1 is replaced wholesale with libsecp256k1-zkp when rust-secp256k1-zkp is in the dependency tree.
We can enforce versions, using symbol renaming. |
Didn't check but AFAIU zkp is superset of thenon-zkp. Therefore we could just vendor zkp one and disable added zkp stuff by default - enable with feature. Am I missing something?
Great motivation! However did a quick look at zkp crate and it seems completely disconnected from upstream. Thus this seems to be a problem of Rust code, not C code because the context type is not interchangeable. |
I am not completely opposed to this, but I think it would be scary and surprising for users if the "Rust bindings to libsecp256k1" actually were bindings to a different project with far fewer eyes on it and a much lower threshold for acceptance of cryptosystems. Yes, it is a superset, so this shouldn't matter ... but "shouldn't matter" isn't enough here, in my opinion.
rust-secp-zkp uses the rust-secp |
It seems odd to me that libsecp256k1-zkp tries to be a replacement for libsecp256k1, rather than just a separate library that has libsecp256k1 as a dependency. I realize it might be difficult to change that design now, but it appears to be what is causing these problems. In the alternative design where -zkp is a separate library, its context object can just hold a pointer to a libsecp256k1 context object. |
It's certainly odd. The main reason for this decision is that libsecp256k1 doesn't expose low-level data types and functions (operation on group or field elements). So if you want to reuse the low-level implementation for your scheme, the natural way is to fork the project and have access to all the internal low-level functionality (simply because it's not internal to your library). The reasons for not exposing low-level functionality include:
Now how can we do better? The goal is to expose the internal functionality somehow. The obvious idea is to split libsecp256k1 into a low-level library and a high-level library. But that seems hard without the willingness to guarantee API stability. Moreover, libsecp256k1 currently uses unity builds (single compilation unit) to help the compiler optimizing. Nowadays, LTO is more common than it was when the library was created but maybe not everyone (e.g., embedded) has great compilers and we don't really checked if there's a performance penalty even with LTO enabled. (Maybe everyone has a decent compiler and this is not an issue -- but we don't have the data.) Another idea is to expose the internal functionality differently, e.g., have a switch that just gives you access to everything. This would be intended to enable "add-ons" such as -zkp, and the switch should not used by end users, and clearly marked as such. That sounds reasonable to me personally because I think it may be better than the current model because then libsecp256k1 could be a real dependency of -zkp. @jonasnick created a PoC of a similar idea here BlockstreamResearch/secp256k1-zkp#153 (It seems again odd that this is a PR to -zkp but I think this just because it's not ready to show yet.) But then there are some questions to answer: Is this a compile-time or a run-time switch? Will we still have LTO issues? Is it then possible to write add-ons in other languages, e.g., Rust? Can we support more than one add-on? Happy to hear any thoughts or other suggestions. We often think about this problem. |
@apoelstra docs.rs isn't showing those types as reexports but looking at the code they are. Weird. Thinking about it again, those megabytes/milliseconds are only spent if the contexts are created and should be unrelated to the library being linked twice. I don't see how it could be UB if the vendored libraries share exactly same base code but I suspect this can't be guaranteed without severely restricting dependencies. Thus I agree with resolving this. If I understand correctly what people said here it should be possible to make zkp source code split into two parts: non-zkp and the add-on. They can still be joined at build time using |
@Kixunil the In the past, we have also had some nontrivial changes in the base code -- our old rangeproof code modified the context objects to include another precomp table, for example. I can't guarantee we'll never do that again. (Though if if we did do that again, it would break our current context-object-sharing hack..) Another concern I have is that secp256k1-zkp is ideally not the only library that tries to add cool/interesting cryptosystems to libsecp. So if we come up with a solution that works for -zkp but not for anybody else, that's not great. |
Do you know anyone who would complain about it? Is the difference worth adding hacks into these libraries?
In that case I see no solution other than exporting internals. If there are two orthogonal libraries then you can't use one to replace another. |
I would complain. It makes me deeply uncomfortable to be pulling -zkp stuff into something which claims to be non-zkp bindings, even if they are not linked and even if it's possible to manually check that the C code is consistent. And there are no hacks in rust-secp, only in rust-secp-zkp.
This is a good point. |
Why? Is the issue possibly misleading name or something else? |
@Kixunil yes, because of the misleading name, and beacuse it would then mean that there are no longer standard Rust bindings for libsecp. libsecp256k1 has much higher standards for code quality, QA/review, and cryptographic soundness than does libsecp256k1-zkp. The fact that secp-zkp is a superset seems to me like an accident of history/ease of maintenance thing for the libsecp-zkp developers, and even if they promised that the libraries would always be compatible in this way (which we do not) and if we were exactly the same set of people (we are not), such promises aren't what users expect when they look for libsecp256k1 bindings. |
Although I prefer to look at actual merits of things and verifying stuff I think I can see that some people would find it fishy. I wonder if it'd be also bad to still include the whole library but provide |
I might be okay with that, but I still dislike the idea of rust-secp-zkp stuff leaking into rust-secp. |
FTR I don't strictly like it either, I just don't see any better solution. |
aa51638 update changelog for 0.22.0 (Andrew Poelstra) d06dd20 update fuzzdummy API to match normal API (Andrew Poelstra) f3d48a2 update "should terminate abnormally" test to trigger a different ARG_CHECK (Andrew Poelstra) 8294ea3 secp256k1-sys: update upstream library (Andrew Poelstra) 2932179 secp256k1-sys: update secp256k1.h.patch (Andrew Poelstra) Pull request description: Should wait on merging until we get a minor release out with #382 and #376. May also want to bundle #380 with this? ACKs for top commit: real-or-random: ACK aa51638 I can't judge if the feature set is meaningful but this release PR is fine Tree-SHA512: e7f48b402378e280a034127f2de58d3127e04303a114f07f294fa3d00c0a083ae0d43375a8a74d226b13ea45fb3fde07d8450790e602bbf9581adc5fd8bc7d29
Hi @TheBlueMatt, do you still stand by this statement? I spent some time recently thinking about this and discussing it with @apoelstra (over here) and we came to the conclusion that a malicious dependency could not set |
If you have a malicious dependency, you're already completely screwed. There's so much stuff malicious dependencies can do that it makes no sense to worry about them setting env vars. Review your deps, verify signatures or whatever you need. |
I need to re-load this discussion into my head, but I've become convinced that malicious deps basically mean that you're screwed. So the reasons to use
There are some parallel discussions about the nature of libsecp vs libsecp-zkp but I don't think that has anything to do with this. (At least, there are other places to discuss it, such as on one of those two repos). I'll just reiterate that I think it's totally reasonable for somebody to trust/whitelist libsecp but not libsecp-zkp, so we can't pull libsecp-zkp directly into this library. So at first glance, I think:
All this to say that I think our best choice, which is still not great, would be to replace |
The
secp256k1-sys
README currently says:However, all this does is not use the bundled symbols by not renaming the Rust references to them. It doesn't prevent those symbols from being built, which becomes pointless work in this user-configured context.
Instead, the
build.rs
could checkRUSTFLAGS
for the presence of--cfg=rust_secp_no_symbol_renaming'
, and simply exit if it is present, since that is a clear indication from the user that they do not want these bundled symbols.As a stretch goal, having this be controlled by a feature flag would enable the
cc
dependency to be dropped as well if not using the bundled symbols, butcc
is a relatively small and self-contained dependency, so that's less of a problem.The text was updated successfully, but these errors were encountered: