Join GitHub today
GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.
Sign upadded secret types rfc #2859
added secret types rfc #2859
Conversation
This comment has been minimized.
This comment has been minimized.
Lokathor
commented
Jan 23, 2020
|
If the LLVM RFC were accepted we'd still need to move Rust to some future version of LLVM that implements that RFC before we'd even have a hope of implementing this RFC. Is that right? |
This comment has been minimized.
This comment has been minimized.
|
We could still implement this RFC, we just wouldn't have all of the guarantees yet. It's still a huge step in a good direction though. |
| - Confidentiality of compromise is desirable | ||
| Therefore, it’s important to use data-invariant programming for secrets. For this reason, secret types would only allow data invariant operations at all levels of compilation. | ||
|
|
||
| Additionally, secret types serve as an indicator to programmers that this information should be treated with care, and not mixed with non-secret data, an invariant that would be enforced by the type system. |
This comment has been minimized.
This comment has been minimized.
colmmacc
Jan 23, 2020
Just some more detail on this: it's important not to mix secrets and untrusted input in a context such as compressed content. Otherwise CRIME-like (https://en.wikipedia.org/wiki/CRIME) attacks can be used to retrieve the secret information.
This comment has been minimized.
This comment has been minimized.
burdges
commented
Jan 23, 2020
|
I'd suggest adding |
| # Rationale and alternatives | ||
| [rationale-and-alternatives]: #rationale-and-alternatives | ||
|
|
||
| The main alternative to this design is to handle this via crates such as secret_integers [3]. However, without compiler integration, the compiler could optimize out the guarantees that have been claimed at the source level. For example, the compiler could use non constant time instructions such as divide. Using these primitives correctly from a high-level language will typically require careful management of memory allocation, flagging relevant memory as containing secret for the operating system, hardware, or other system level components. |
This comment has been minimized.
This comment has been minimized.
Centril
Jan 23, 2020
Member
I could not find what guarantees were claimed at the source level in this RFC.
| # Guide-level explanation | ||
| [guide-level-explanation]: #guide-level-explanation | ||
|
|
||
| Secret integer types are a type of restricted integer primitive. In particular, non-constant time operations like division are prohibited. Printing of secret integers directly is also prohibited--they must first be declassified into non-secret integers. |
This comment has been minimized.
This comment has been minimized.
Centril
Jan 23, 2020
•
Member
Why are these primitive types as opposed to e.g. exposed intrinsics on normal integer types for which wrappers can be built around?
This comment has been minimized.
This comment has been minimized.
kennytm
Jan 24, 2020
Member
Using wrapper type means you can't write let x: Secret<u8> = 123; (unless Rust has user-defined literal). Though let x = Secret::new(123_u8); doesn't look bad.
This comment has been minimized.
This comment has been minimized.
bstrie
Jan 27, 2020
Contributor
Yes, SecretU8::new(123); is much more in line with what I would expect. A relevant precedent is the NonZero* types: rather than having nonzero_i8 and friends as primitives, we have NonZeroI8 with new constructors.
If the RFC desires to insist on primitives rather than library types, I would expect a very extensive section documenting why that would be necessary; as it stands, adding a new primitive has a much higher bar than adding a new library type.
This comment has been minimized.
This comment has been minimized.
Tom-Phinney
Jan 27, 2020
Using wrapper type means you can't write
let x: Secret<u8> = 123;(unless Rust has user-defined literal). Thoughlet x = Secret::new(123_u8);doesn't look bad.
I think that would be completely acceptable, documenting the secret nature of all such data. On the issue of primitive vs library types, LLVM will need to treat Secret types distinctively, without conditional branches and with clearing of state post-use, at least to the extent that proves to be practical. I doubt that library types can get passed to LLVM with a distinguisher that triggers those code-generation aspects.
| - Default | ||
| - Not | ||
|
|
||
| We will also need to define a trait Classify<T> for T and method declassify. Classify will take a non secret integer or boolean and return a secret integer or boolean. `declassify` will consume a secret integer returning a non-secret integer. |
This comment has been minimized.
This comment has been minimized.
Centril
Jan 23, 2020
Member
I think it would make for a more readable proposal if you specified the trait definitions, and the other operations in ```rust blocks.
|
|
||
|
|
||
| # Reference-level explanation | ||
| [reference-level-explanation]: #reference-level-explanation |
This comment has been minimized.
This comment has been minimized.
| # Drawbacks | ||
| [drawbacks]: #drawbacks | ||
|
|
||
| Because secret integers prohibit the use of certain operations and compiler optimizations, there will be a performance impact on code that uses them. However, this is intentional. |
This comment has been minimized.
This comment has been minimized.
Centril
Jan 23, 2020
Member
This is vague. Please be as specific as possible regarding what optimizations or categories of optimizations would be prohibited, at what granularity they would be prohibited, and what the impacts of those prohibitions would be.
Please also, without referring to LLVM, specify how these prohibitions are justified in terms of a specification. If guarantees (with stability promises and all that, as opposed to best effort constructs) are sought after, such a specification should be operational (in the sense of operational semantics and abstract machines). At the moment, I do not see how this can be specified in terms of an abstract machine / interpreter like Miri.
This comment has been minimized.
This comment has been minimized.
RalfJung
Jan 28, 2020
•
Member
I imagine one could re-use some ideas from this paper and more generally from the IFC (information flow control) literature. The guarantee should probably be some form of non-interference -- program executions that only differ in secrets must not be observably different, or so. One hard problem is defining what exactly the possible observations are; that can and should (IMO) be done in an operational way (a "trace of observations" that e.g. Miri could print, or so).
A type-based approach certainly sounds like a good start for me; this feels much better than ad-hoc attributes.
This comment has been minimized.
This comment has been minimized.
Tom-Phinney
Jan 28, 2020
Thanks for the paper reference; that's a good start. Rather than just enumerate possible observations, we can also enumerate observation modalities that are out of scope. My list of the latter includes
- instantaneous power fluctuations due to Hamming weight of secret data, whether on-chip or at interconnects between chips (e.g., CPU and RAM); and
- secret-influenced variation in RF radiation as secret data propagates through the circuitry.
This comment has been minimized.
This comment has been minimized.
RalfJung
Jan 28, 2020
Member
I think you made a very good point for why we need to precisely list what we do consider an observation -- everything not on that list (and that will be all sorts of weird stuff, like your two items) is not protected.
This comment has been minimized.
This comment has been minimized.
tarcieri
Jan 28, 2020
I think you can generally put all microarchitectural covert channels out-of-scope, with the possible caveat that writing constant-time code is the best known defense against them.
That said I think compiling a list of more general architecture properties is a good idea. An example of one is: the underlying architecture is assumed to be able to perform integer multiplication in constant time (e.g. doesn't short-circuit on multiply-by-0-or-1)
This comment has been minimized.
This comment has been minimized.
cryptojedi
Jan 29, 2020
That said I think compiling a list of more general architecture properties is a good idea. An example of one is: the underlying architecture is assumed to be able to perform integer multiplication in constant time (e.g. doesn't short-circuit on multiply-by-0-or-1)
I don't think such a strong assumption is required. Once the information that inputs to a multiplication are secret is forwarded to the compiler, it's the compiler's job to generate constant-time code. If there is a constant-time multiplier in the target micro-architecture, that's easy. If not, what the compiler will need are constant-time runtime libraries to use for arithmetic on secret integers. The critical job for the language to take care of is to forward the information about secrecy in the first place.
This comment has been minimized.
This comment has been minimized.
tarcieri
Jan 29, 2020
If there is a constant-time multiplier in the target micro-architecture, that's easy. If not, what the compiler will need are constant-time runtime libraries to use for arithmetic on secret integers.
Even across the same microarchitecture there are CPUs that do-or-do-not perform multiply in constant time, e.g. the PowerPC 7xx CPUs used by Apple perform constant time multiplication, but many other PPC32 CPUs do not.
This comment has been minimized.
This comment has been minimized.
cryptojedi
Jan 29, 2020
I agree that there is a difference between different microarchitectures (i.e., implementations of some architecture) with only some having variable-time arithmetic instructions. The situation is even worse if you take future implementations of some architecture into account, that suddenly introduce new variable-time behavior. Compilers will need a new hardware-software contract to know what subset of instructions is safe to use (see https://ts.data61.csiro.au/publications/csiro_full_text/Ge_YH_18.pdf).
However, to me this all seems like lower-level issues: what can be done on language-level is to ensure that some operations are avoided on secret data that a compiler cannot possibly protect (branching, addressing) and for all other operations leave it to the compiler and hardware to guarantee constant-time behavior.
This comment has been minimized.
This comment has been minimized.
Tom-Phinney
Jan 29, 2020
Nice find! Here's a more recent one: https://arxiv.org/abs/1901.08338.
One of the authors of that paper also contributed to this recent attack, which shows that Intel's attempt at ad-hoc patching is useless: https://cacheoutattack.com/
RIDL is another good attack, well written paper: https://mdsattacks.com/
This comment has been minimized.
This comment has been minimized.
atrieu
Jan 30, 2020
I imagine one could re-use some ideas from this paper and more generally from the IFC (information flow control) literature. The guarantee should probably be some form of non-interference -- program executions that only differ in secrets must not be observably different, or so. One hard problem is defining what exactly the possible observations are; that can and should (IMO) be done in an operational way (a "trace of observations" that e.g. Miri could print, or so).
Co-author of said paper here.
Cryptographic constant-time security is indeed usually stated as a non-interference property. This works by taking the operational semantics of the considered language, and instrumenting it by adding "leakages" or "observations" to step taken by instructions that can be compiled to some sort of jump or memory accesses. A program is then said to be secure if for two executions that differ only on secret inputs, then the leakages are equal.
As for impacted optimizations, surprisingly, the only thing impacted during our work on CompCert was the generation of builtin code that was not branchless, so it might be safe to consider those optimizations implemented in CompCert to not be impacted for llvm too: tailcall, inlining, CSE, constant propagation, deadcode elimination. That remains a lot less than the optimizations implemented in llvm though, I assume...
| [unresolved]: #unresolved-questions | ||
| Out of scope: Memory zeroing (beyond implementing the drop trait) and register spilling | ||
|
|
||
| There is an in progress RFC for LLVM that will be required for the secret type guarantees to be fully guaranteed throughout the compilation process. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
tarcieri
Jan 23, 2020
If Cranelift were to add support for this, the earlier the better.
It's going to be a monumental effort for LLVM, not because the work is complicated - it's simple and mundane, but there is going to be a lot of it because it touches every layer of code generation.
This comment has been minimized.
This comment has been minimized.
tarcieri
Jan 23, 2020
Also note that in as much as Cranelift is intended to be a backend for a WASM runtime, there are parallel efforts to add similar features to WASM, like CT-WASM:
https://github.com/PLSysSec/ct-wasm
Ideally if this does end up finding its way into WASM proper, Cranelift could use a common implementation for both Rust secret integers and WASM secret integers.
This comment has been minimized.
This comment has been minimized.
Centril
Jan 23, 2020
•
Member
Great. This should be elaborated upon in the text. And specifically, I think guarantees are sought (which I am skeptical of, see above), I think the same guarantees must be given in Cranelift as well before this can become stable.
This comment has been minimized.
This comment has been minimized.
bjorn3
Jan 23, 2020
•
cc bytecodealliance/cranelift#1327 (not exactly the same, but for the same purpose)
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
bjorn3
Jan 23, 2020
I think for this a special type may be better though to also prevent any branches, non constant time multiplication and other ways of leaking it.
| - ShrAssign | ||
| - Sub | ||
| - SubAssign | ||
| - Sub |
This comment has been minimized.
This comment has been minimized.
bjorn3
Jan 23, 2020
I think these should only be implemented when the size is at most the native pointer size to eliminate most non constant time implementations.
| - Overflowing_shl | ||
| - Overflowiing_shr | ||
| - Overflowing_pow | ||
| - Pow |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
dsprenkels
Jan 24, 2020
I don't seem to understand why this cannot be constant time. Can you elaborate?
This comment has been minimized.
This comment has been minimized.
bjorn3
Jan 24, 2020
It is implemented using a loop and if's: https://doc.rust-lang.org/stable/src/core/num/mod.rs.html#3667
pub fn pow(self, mut exp: u32) -> Self {
let mut base = self;
let mut acc = 1;
while exp > 1 {
if (exp & 1) == 1 {
acc = acc * base;
}
exp /= 2;
base = base * base;
}
// Deal with the final bit of the exponent separately, since
// squaring the base afterwards is not necessary and may cause a
// needless overflow.
if exp == 1 {
acc = acc * base;
}
acc
}
This comment has been minimized.
This comment has been minimized.
dsprenkels
Jan 24, 2020
Ah, I see what you mean. I misread this as "cannot be implemented in constant-time".
In any case, we could pow to do the computation in constant time. (example)
This comment has been minimized.
This comment has been minimized.
burdges
Jan 24, 2020
You need more code for constant time, like https://github.com/dalek-cryptography/curve25519-dalek/blob/master/src/backend/serial/u64/field.rs#L444 but maybe anyone using secret data already does this themselves.
This comment has been minimized.
This comment has been minimized.
programmerjake
Jan 30, 2020
I would allow base to be secret, but not exp. That way, it is trivial to do it in time that only depends on exp -- the existing pow algorithm is good enough. If secret exp is desired, you should just use a cryptography crate -- out-of-scope for std due to complexity.
| - Overflowing_mul | ||
| - Overflowing_neg | ||
| - Overflowing_shl | ||
| - Overflowiing_shr |
This comment has been minimized.
This comment has been minimized.
| - Default | ||
| - Drop | ||
| - Mul | ||
| - MulAssign |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Lokathor
Jan 23, 2020
- and / or / xor should be constant time
- copy has no machine instructions it's just semantic
- clone is just copy for copy types
- Default just pulls pulls out a 0 for integers
- Drop does nothing probably, or it could volatile write 0 or something, which is still constant time.
Add and Mul I don't know enough to say in all cases.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Tom-Phinney
Jan 23, 2020
•
Mul, and thus MulAssign, are not constant-time on ARM Cortex or Power/PowerPC. Many implementations use smaller multipliers in HW and then stitch the partial products together over multiple clock cycles. Most such implementations allow some of the clock cycles to be skipped for certain data. The most common is early termination if either input is zero. The next most common early termination case is if the upper half of either input is zero. The ARM Cortex family multipliers have an even more complex scheme; it appears to be undocumented, but testing indicates that the Cortex multipliers early terminate whenever the upper half of either input has a Hamming weight less than 2 or more than 14 (for 32x32 multiply).
This comment has been minimized.
This comment has been minimized.
Tom-Phinney
Jan 23, 2020
•
Many architectures have a constant-time means of converting a status bit (usually carry) to an all-zero or all-one mask. On those architectures conditional if/then/else logic can be implemented by xor/masked-and/xor selection to run in constant time. The LLVM IR has the needed primitive, but LLVM usually realizes that primitive as a varying-execution-time conditional branch. That conversion would need to be suppressed for conditional expressions involving these secret types. Among other things, that should enable constant-time for Saturating_add and Saturating_sub.
This comment has been minimized.
This comment has been minimized.
agl
Jan 24, 2020
The constant-time behaviour of multiplication can indeed be a problem on some CPUs, although it's very often safe. Much more information is at https://www.bearssl.org/ctmul.html
However, multiplication is extremely useful! Omitting it at one layer likely means that people will simulate it one layer up. (I.e. people will implement multiplication in Rust code using addition etc.) LLVM knows the CPU target, can know the behaviour of mul on that target, and is likely best placed to substitute a constant-time replacement when needed.
This comment has been minimized.
This comment has been minimized.
tarcieri
Jan 24, 2020
•
I think if you're using a CPU which e.g. short-circuits on multiply-by-zero or multiply-by-one, it's basically broken for cryptographic purposes (e.g. the aforementioned PPC32)
I have no expectation of Rust or LLVM to try to autodetect these CPUs or apply some sort of countermeasure, but would rather explicitly call out CPU-level assumptions about how e.g. multiplication must work, hopefully as a standard set of boilerplate which can be copied-and-pasted into Rust libraries which rely on those set of assumptions.
| # Summary | ||
| [summary]: #summary | ||
|
|
||
| The goal is to provide primitive data types that can be used to traffic in transient secrets in code that are important to not accidentally leak. These new primitives would be used in conjunction with an in-progress LLVM RFC to ensure important security invariants like secret-independent runtime are maintained throughout the compilation process. Note that we explicitly do not want secret_isize and secret_usize, because we do not want to index based on secrets. |
This comment has been minimized.
This comment has been minimized.
| ``` | ||
| let x : secret_i8 = 6; | ||
| let y : secret_i8 = 10; | ||
| println!((x ^ y).declassify()) |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
cryptojedi
Jan 24, 2020
Explicitly copy the secret type to a public type. As an example, think of a ciphertext, which depends on a secret key and a secret message and is thus secret, being declassified before being sent over a channel.
This comment has been minimized.
This comment has been minimized.
|
|
This comment has been minimized.
This comment has been minimized.
cryptojedi
commented
Jan 24, 2020
The term "secret" characterizes a property of the type or data. What guarantees the language can or should offer for this property is an interesting second question. Not leaking information through timing is one, but I agree that another property that (longer-term, once LLVM or cranelift have support for this) should probably be guaranteed is to zero memory and registers once the data is no longer used. |
This comment has been minimized.
This comment has been minimized.
Tom-Phinney
commented
Jan 24, 2020
|
I've been interpreting "secret" in this context as "disclosure resistant", including disclosure via timing side-channel attacks and (to the extent feasible) via data remnants in memory and registers. Note that other side-channels, such as instantaneous power fluctuations due to Hamming weight of a multiplier, are not covered. |
This comment has been minimized.
This comment has been minimized.
burdges
commented
Jan 24, 2020
•
|
I've no problem with the word secret here because for an individual We could specify this does no zeroing right now, but express an interest in register zeroing, explicitly leave stack zeroing for unclear future work, and plan that heap allocations should always deal with zeroing themselves. @Centril If you want wrappers then maybe a trait works better:
|
This comment has been minimized.
This comment has been minimized.
egilburg
commented
Jan 25, 2020
•
|
Instead of restricting division, perhaps the compiler should guarantee constant-time division (even if it means sometimes slowing down an otherwise faster computation to match the slow path?) Also, "secret" is rather vague and could mean different things to different consumers. What is needed? Constant time operations? Guaranteed memory blanking on out-of-scope? What about other cases? For example, Ruby for a while used $SAFE for some related "secrecy" and had a bunch of interesting ideas (such as not allowing data from external sources like IO to be assigned to a "safe" variable, although that was later dropped for various technical reasons and low adoption. Could IO sanitization also be useful here? And what if someone say builds a banking app and wants to store transactions as secrets (but requires features such as division)? They could assume |
This comment has been minimized.
This comment has been minimized.
lovesegfault
commented
Jan 25, 2020
|
This seems relevant, as a reference if nothing else: https://www.bearssl.org/constanttime.html |
This comment has been minimized.
This comment has been minimized.
burdges
commented
Jan 25, 2020
|
I've normally seen separate manual inverse and reduce implementations, not manual division implementations. I've never seen a batch reduce operation, but batch inversion is far more efficient than one off inversions. It'd maybe resemble https://github.com/dalek-cryptography/curve25519-dalek/blob/master/src/scalar.rs#L761 so Yes, there is no reason for a banking app to store "transactions" as secrets because "transactions" are not usually processed often enough for details to leak. These secrets are secret keys like elliptic curve scalars or stream cipher keys, so we run the data through identical-ish computations millions of times, and a compromise does unquantifiable damage. |
This comment has been minimized.
This comment has been minimized.
|
This RFC is missing an explicit discussion of all the various "secrecy properties" like constant-time operations and zeroing-on-drop, and how considering all of them leads to this particular design. I think it's suggesting they should all be covered by a single set of types because anytime you want one you want all the others, but it should explicitly argue that. And it should be much clearer about saying that zeroing-on-drop is something we want secret types to have, but it has to be a target-specific/backend-specific guarantee, although we need a surface-level type distinction to allow backends to reliably provide anything of the sort (that is true, right?). Just saying that zeroing is "out of scope" makes it sound like it's completely irrelevant to this RFC, but IIUC it's a critical part of the argument we want "secret types" at all as opposed to, for example, a bunch of functions like |
This comment has been minimized.
This comment has been minimized.
tarcieri
commented
Jan 25, 2020
•
|
Speaking as the author of the The main problem is: when do you zero? If you do it with traditional "drop" semantics, i.e. zero every intermediate secret value produced on every stack frame, you're introducing a lot of overhead to zero out values that might be erased by subsequent stack frames within the same overall secret handling computation, e.g. an implementation of a cryptographic algorithm. This has lead to some real-world performance problems with people who have tried to use An alternative idea is to handle this sort of zeroization not at the level of individual values, but at the level of function calls via a "stack bleaching" approach using a special attribute which wipes the entire stack only after an algorithm (which may call other functions, potentially via FFI) has completed executing: #[sensitive]
pub fn secret_key_op(key: &Key, plaintext: &[u8]) -> Vec<u8> {
[...]
}(note there's ample discussion about this approach on the linked rust-internals thread) The reason why this RFC placed zeroization discussion out-of-scope for now is because secret types providing some resilience against leaking secrets via sidechannels/covert channels are useful in-and-of-themselves even without an associated zeroization story, and a language-level zeroization story is a tricky enough topic it could perhaps use its own separate RFC, with some research into the tradeoffs of value-based vs "stack bleaching" approaches. |
This comment has been minimized.
This comment has been minimized.
cryptojedi
commented
Jan 27, 2020
This is a dangerous assumption. I don't think that programmers, in particular outside of the very specialized area of crypto code, should have to worry about /how/ secret their data is. They should have a means to express that data is "secret" and then programming language and compiler protect that data from leaking. In some cases this will come with performance penalties and in some cases those penalties will be serious, but my intuition is that introducing types for different levels of secrecy and teaching programmers what exactly those mean is much to complex and error prone. |
This comment has been minimized.
This comment has been minimized.
tarcieri
commented
Jan 27, 2020
|
In our initial discussions around this feature some interesting use cases came up, including things like rendering UIs that contain or accept secrets, such as credentials (e.g. PINs or pairing codes). That's a pretty broad scope that encompasses much more than cryptography, and whether or not that's a good idea is debatable. I believe these should be the go-to types for anything it's desirable not to leak via sidechannels/covert channels. |
This comment has been minimized.
This comment has been minimized.
burdges
commented
Jan 27, 2020
|
We'll never have As part of stabilization, we could explore traits like |
This comment has been minimized.
This comment has been minimized.
Lokathor
commented
Jan 27, 2020
|
Why, precisely, would we not ever have |
This comment has been minimized.
This comment has been minimized.
burdges
commented
Jan 28, 2020
|
You cannot iterate over the |
This comment has been minimized.
This comment has been minimized.
tarcieri
commented
Jan 28, 2020
•
|
UTF-8 is a prefix varint format, and therefore the length prefixes are relatively easy to decode in constant-time/without branching. However, subsequent accesses are dependent on those lengths, and at the very least it would leak the character width through a fairly observable sidechannel. Whether or not this matters depends on your use case. The lower ASCII subset of UTF-8 is safe. I imagine there are a million other methods on |
This comment has been minimized.
This comment has been minimized.
Tom-Phinney
commented
Jan 28, 2020
|
On most architectures, real conditional branches based on secret data are vulnerable to a timing side-channel attack. In those cases alternative formulations for However, other conditional branches (not based on secret data) are okay. Since "security by obscurity" is insecure, unconditionally looping over all eight bytes in a |
This comment has been minimized.
This comment has been minimized.
Lokathor
commented
Jan 28, 2020
|
Unless, and call me crazy here, you just store the data in There is actually world beyond all utf-8 all the time. |
This comment has been minimized.
This comment has been minimized.
burdges
commented
Jan 28, 2020
•
|
We've one family of alternatives worth a brief comment: We introduce "type attributes" or "meta traits" that rustc infers only after type inference and checking, so
I think this approach incurs far too much strangeness in the langauge and too much complexity in rustc, even despite delaying until the end of type checking, but seemed worth noting. |
This comment has been minimized.
This comment has been minimized.
Tom-Phinney
commented
Jan 29, 2020
|
The more I think about this proposed RFC, the more I conclude that the need is for an attribute that propagates with expression computation, rather than new types. Otherwise there will be a myriad of new types, including Also, the non-disclosure need might prove to be more than binary, since long-lived secrets typically need more extensive protection than ephemeral secrets. For example, storage for ephemeral secrets might not need to be cleared on drop. Such considerations lead to multiple classification levels, perhaps resembling the well-known Confidential, Secret and Top Secret. I don't want to contemplate |
This comment has been minimized.
This comment has been minimized.
tarcieri
commented
Jan 29, 2020
Sounds like you want something like the However that concept is orthogonal to and somewhat different from the goals of this RFC, which are more closely aligned to something like the |
This comment has been minimized.
This comment has been minimized.
Tom-Phinney
commented
Jan 29, 2020
|
It's not what I want; I'm retired and no longer write such code. It's what I suspect is needed.
Otherwise we find ourself in the traditional engineering situation of having a solution in search of a market. |
This comment has been minimized.
This comment has been minimized.
tarcieri
commented
Jan 29, 2020
•
|
The requirements are covered in the RFC's summary:
This RFC is about specifying secret integer types which by-design are incapable of any operations which would expose their values through timing sidechannels, and in conjunction with LLVM (or e.g. Cranelift) support ensure subsequent compilation/optimization passes do not introduce such sidechannels. |
This comment has been minimized.
This comment has been minimized.
Aloso
commented
Jan 30, 2020
|
I'm not an expert, but I believe it should be possible to create integer types whose operations execute in constant time, without LLVM intrinsics. How do existing cryptographic softwares (OpenSSL, BoringSSL, rustls, ...) deal with this problem? |
This comment has been minimized.
This comment has been minimized.
cryptojedi
commented
Jan 30, 2020
You do not need instrinsics, but LLVM needs to know that these types are secret to emit constant-time code (for example, not introduce branches on those types, not use optimized variable-time arithmetic through runtime libraries, etc.).
All cryptographic code written in higher-level languages than assembly makes an effort to try to use code that compilers don't screw up and then essentially hope for the best. |
avadacatavra commentedJan 23, 2020
•
edited by XAMPPRocky
Rendered
The goal is to provide primitive data types that can be used to traffic in transient secrets in code that are important to not accidentally leak. These new primitives would be used in conjunction with an in-progress LLVM RFC to ensure important security invariants like secret-independent runtime are maintained throughout the compilation process. Note that we explicitly do not want secret_isize and secret_usize, because we do not want to index based on secrets.