Date: Sat, 2 Mar 2024 10:39:21 +1100
I rarely jump into these discussions, but that list of cons for
secure_memset seems like a bunch of extreme edge cases. However the
important thing is that if an exploit has a read primitive then they can
read secrets which still live within memory from within the processes
address space, be it on the stack or elsewhere in memory.
It's important to separate write primitives which is where you can get
things like remote code execution and has the potential to do some of what
your things say from read primitives which can just read memory. In many
situations the read and write primitives are different exploits and
sometimes they might have certain restrictions on each of those primitives
(e.g. not nulls, only stack, only within a certain range, etc).
> It will get swapped to disk
This seems like a strawman argument, it _could_ get swapped out to disk,
but typically your getting the secret using it then clearing it, for it to
be swapped out to disk the OS needs to have determined that between that
period of time that it felt it was crucial to swap it to disk which is
going to be rare, and only in a multi-threaded environment would something
inside that process see it.
> memset will zero out the L1 *cache* but fail to zero out the L2/L3 cache
or main memory
This seems like another strawman argument, most read primitives are just
that, a read primitive, I would love to see what technique you'll use for a
common read primitive to gain access to that memory with a common read
primitive? I assume this would be hoping to use different threads and race
for it hoping that cache coherence doesn't get in the way. At the same time
why don't we account for someone sniffing the network, reading from disk,
etc.
> your magic memset will zero out the buffer you pointed it at, but it
won't zero out the other copies of that same data that the optimizer caused
to be made (trivially, thus undetectably) all over the stack, under the
As-If Rule
This is the only semi-reasonable one in your list, however I'm not certain
that it really stands up much either. Your secrets are often strings, they
will typically be stored either on the stack or heap and then all accesses
will go through that, parts might end up in registers for example copying,
calculating a length, etc. But your typical read primitive isn't going to
let you read registers, you'll need remote code execution for that.
The API you use might make copies (e.g. copy to a buffer to send over the
network) but that's within the control of the person writing the code (and
whatever libraries they use).
This is where instead of guaranteeing that it will be removed which is
impossible, it should be implementation defined and make the best effort to
eliminate it, which could just be zeroing the place in memory where it
lives, which is typically going to be one place.
> the attacker will just read that memory *before* the memset, instead of
after
This implies that the attacker has access to another thread, is able to
find the region in memory that it lives in, do it within that small window
of time that it exists in memory, it's another edge case which _could_
happen but it's not the common case, should we throw out ASLR because
someone could just read the cookie? No because it makes it harder to
exploit.
> Case 1 is you're writing an important program that *will be attacked* in
these horrible ways, such that you can't even leave secrets lying around in
your own process's address space, and then secure_memset won't help because
it doesn't stop all these other channels; your only sensible approach is to
work very carefully with extremely low-level code.
"won't help" is a pretty strong statement which I feel is wrong, will it be
a silver bullet? No and probably nothing will be but it will help remove
secrets from memory. I don't think we should let perfect get in the way of
implementing an obvious solution which has been done by industry for quite
a while. Do you have any CVEs or realistic PoCs to show that cases where
things like secure_memset have been used have still led to secrets leaking?
Or is it just all hypothetical?
Thanks,
James Mitchell
On Sat, 2 Mar 2024 at 07:33, Arthur O'Dwyer via SG14 <sg14_at_[hidden]>
wrote:
> On Fri, Mar 1, 2024 at 3:10 PM Robin Rowe via SG14 <sg14_at_[hidden]>
> wrote:
>
>> > As far as C++ standard is concerned, this proposal would only make
>> sense if you mean protect access to memory assigned within the same
>> application from another section that has gone rogue.
>>
>> Yes.
>> [...]
>> The page_protected_password is in its own bubble. A programmer can't
>> land in the page_protected_password buffer by overrunning some nearby
>> pointer like username. Nor can the programmer overrun the
>> page_protected_password buffer without a segfault or signal triggered.
>>
>> You are right if thinking that get_pass(page_protected_password), or any
>> function passed that pointer, may do whatever it likes with
>> page_protected_password. Being memory safe gives protection from buffer
>> overruns, doesn't mean error-proof.
>>
>
> Robin,
> Your idea, especially in the focus on "security," sounds an awful lot like P1315
> "secure_clear"
> <https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1315r7.html>,
> which was rejected by C++ but according to the GitHub tracker
> <https://github.com/cplusplus/papers/issues/67> was actually adopted by
> WG14 for C23.
> The problem with "secure_clear" — which was supposed to behave like
> memset(buf, '\0', sizeof(buf)) except "do what I mean" in the case that
> `buf` was dead after the write — is that it doesn't actually help any
> production use-case as far as we could tell.
>
> Con: "If you're using this thing to zero out a password, or something,
> you're definitely doing it wrong, because you failed to pin the buffer's
> page in RAM. It will get swapped to disk, your magic memset won't zero out
> the *swapfile*, and you're just as pwned as if you had used ordinary
> memset in the first place. Or, your magic memset will zero out the L1
> *cache* but fail to zero out the L2/L3 cache or main memory. Or, your
> magic memset will zero out the buffer you pointed it at, but it won't zero
> out the other copies of that same data that the optimizer caused to be made
> (trivially, thus undetectably) all over the stack, under the As-If Rule.
> Or, the attacker will just read that memory *before* the memset, instead
> of after."
>
> Pro: "Belt-and-suspenders! Swiss cheese security model! We certainly need
> more work on things like an API for pinning memory pages, but secure_memset
> can't possibly *hurt!*"
>
> Con: "Look, there are only two cases to consider here. Case 1 is you're
> writing an important program that *will be attacked* in these horrible
> ways, such that you can't even leave secrets lying around in your own
> process's address space, and then secure_memset won't help because it
> doesn't stop all these other channels; your only sensible approach is to
> work very carefully with extremely low-level code. Case 2 is you're *not*
> writing that kind of program, and then secure_memset won't help because you
> haven't got that problem to begin with. Either way, it doesn't help you, so
> there's no point in standardizing it."
>
> But I guess WG14 wasn't swayed by the Con arguments, because `memset_explicit`
> is in C23 for real <https://en.cppreference.com/w/c/string/byte/memset>.
> The entire formal wording is as follows:
>
> The memset_explicit function copies the value of c (converted to an
>> unsigned char) into each of the first n characters of the object pointed to
>> by s. The purpose of this function is to make sensitive information stored
>> in the object inaccessible. [Footnote: The intention is that the memory
>> store is always performed (i.e. never elided), regardless of optimizations.
>> This is in contrast to calls to the memset function. —end footnote]
>
>
> Your proposed feature is shaped more like "protection from reads from
> within the same process," which I don't even understand what that would
> mean, really; but you should definitely at least read and
> study-the-history-of the `secure_clear` proposal. The discussions
> (including on the SG14 list) will hit a lot of the same points as they
> would for your proposal.
>
> HTH,
> Arthur
> _______________________________________________
> SG14 mailing list
> SG14_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/sg14
>
secure_memset seems like a bunch of extreme edge cases. However the
important thing is that if an exploit has a read primitive then they can
read secrets which still live within memory from within the processes
address space, be it on the stack or elsewhere in memory.
It's important to separate write primitives which is where you can get
things like remote code execution and has the potential to do some of what
your things say from read primitives which can just read memory. In many
situations the read and write primitives are different exploits and
sometimes they might have certain restrictions on each of those primitives
(e.g. not nulls, only stack, only within a certain range, etc).
> It will get swapped to disk
This seems like a strawman argument, it _could_ get swapped out to disk,
but typically your getting the secret using it then clearing it, for it to
be swapped out to disk the OS needs to have determined that between that
period of time that it felt it was crucial to swap it to disk which is
going to be rare, and only in a multi-threaded environment would something
inside that process see it.
> memset will zero out the L1 *cache* but fail to zero out the L2/L3 cache
or main memory
This seems like another strawman argument, most read primitives are just
that, a read primitive, I would love to see what technique you'll use for a
common read primitive to gain access to that memory with a common read
primitive? I assume this would be hoping to use different threads and race
for it hoping that cache coherence doesn't get in the way. At the same time
why don't we account for someone sniffing the network, reading from disk,
etc.
> your magic memset will zero out the buffer you pointed it at, but it
won't zero out the other copies of that same data that the optimizer caused
to be made (trivially, thus undetectably) all over the stack, under the
As-If Rule
This is the only semi-reasonable one in your list, however I'm not certain
that it really stands up much either. Your secrets are often strings, they
will typically be stored either on the stack or heap and then all accesses
will go through that, parts might end up in registers for example copying,
calculating a length, etc. But your typical read primitive isn't going to
let you read registers, you'll need remote code execution for that.
The API you use might make copies (e.g. copy to a buffer to send over the
network) but that's within the control of the person writing the code (and
whatever libraries they use).
This is where instead of guaranteeing that it will be removed which is
impossible, it should be implementation defined and make the best effort to
eliminate it, which could just be zeroing the place in memory where it
lives, which is typically going to be one place.
> the attacker will just read that memory *before* the memset, instead of
after
This implies that the attacker has access to another thread, is able to
find the region in memory that it lives in, do it within that small window
of time that it exists in memory, it's another edge case which _could_
happen but it's not the common case, should we throw out ASLR because
someone could just read the cookie? No because it makes it harder to
exploit.
> Case 1 is you're writing an important program that *will be attacked* in
these horrible ways, such that you can't even leave secrets lying around in
your own process's address space, and then secure_memset won't help because
it doesn't stop all these other channels; your only sensible approach is to
work very carefully with extremely low-level code.
"won't help" is a pretty strong statement which I feel is wrong, will it be
a silver bullet? No and probably nothing will be but it will help remove
secrets from memory. I don't think we should let perfect get in the way of
implementing an obvious solution which has been done by industry for quite
a while. Do you have any CVEs or realistic PoCs to show that cases where
things like secure_memset have been used have still led to secrets leaking?
Or is it just all hypothetical?
Thanks,
James Mitchell
On Sat, 2 Mar 2024 at 07:33, Arthur O'Dwyer via SG14 <sg14_at_[hidden]>
wrote:
> On Fri, Mar 1, 2024 at 3:10 PM Robin Rowe via SG14 <sg14_at_[hidden]>
> wrote:
>
>> > As far as C++ standard is concerned, this proposal would only make
>> sense if you mean protect access to memory assigned within the same
>> application from another section that has gone rogue.
>>
>> Yes.
>> [...]
>> The page_protected_password is in its own bubble. A programmer can't
>> land in the page_protected_password buffer by overrunning some nearby
>> pointer like username. Nor can the programmer overrun the
>> page_protected_password buffer without a segfault or signal triggered.
>>
>> You are right if thinking that get_pass(page_protected_password), or any
>> function passed that pointer, may do whatever it likes with
>> page_protected_password. Being memory safe gives protection from buffer
>> overruns, doesn't mean error-proof.
>>
>
> Robin,
> Your idea, especially in the focus on "security," sounds an awful lot like P1315
> "secure_clear"
> <https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1315r7.html>,
> which was rejected by C++ but according to the GitHub tracker
> <https://github.com/cplusplus/papers/issues/67> was actually adopted by
> WG14 for C23.
> The problem with "secure_clear" — which was supposed to behave like
> memset(buf, '\0', sizeof(buf)) except "do what I mean" in the case that
> `buf` was dead after the write — is that it doesn't actually help any
> production use-case as far as we could tell.
>
> Con: "If you're using this thing to zero out a password, or something,
> you're definitely doing it wrong, because you failed to pin the buffer's
> page in RAM. It will get swapped to disk, your magic memset won't zero out
> the *swapfile*, and you're just as pwned as if you had used ordinary
> memset in the first place. Or, your magic memset will zero out the L1
> *cache* but fail to zero out the L2/L3 cache or main memory. Or, your
> magic memset will zero out the buffer you pointed it at, but it won't zero
> out the other copies of that same data that the optimizer caused to be made
> (trivially, thus undetectably) all over the stack, under the As-If Rule.
> Or, the attacker will just read that memory *before* the memset, instead
> of after."
>
> Pro: "Belt-and-suspenders! Swiss cheese security model! We certainly need
> more work on things like an API for pinning memory pages, but secure_memset
> can't possibly *hurt!*"
>
> Con: "Look, there are only two cases to consider here. Case 1 is you're
> writing an important program that *will be attacked* in these horrible
> ways, such that you can't even leave secrets lying around in your own
> process's address space, and then secure_memset won't help because it
> doesn't stop all these other channels; your only sensible approach is to
> work very carefully with extremely low-level code. Case 2 is you're *not*
> writing that kind of program, and then secure_memset won't help because you
> haven't got that problem to begin with. Either way, it doesn't help you, so
> there's no point in standardizing it."
>
> But I guess WG14 wasn't swayed by the Con arguments, because `memset_explicit`
> is in C23 for real <https://en.cppreference.com/w/c/string/byte/memset>.
> The entire formal wording is as follows:
>
> The memset_explicit function copies the value of c (converted to an
>> unsigned char) into each of the first n characters of the object pointed to
>> by s. The purpose of this function is to make sensitive information stored
>> in the object inaccessible. [Footnote: The intention is that the memory
>> store is always performed (i.e. never elided), regardless of optimizations.
>> This is in contrast to calls to the memset function. —end footnote]
>
>
> Your proposed feature is shaped more like "protection from reads from
> within the same process," which I don't even understand what that would
> mean, really; but you should definitely at least read and
> study-the-history-of the `secure_clear` proposal. The discussions
> (including on the SG14 list) will hit a lot of the same points as they
> would for your proposal.
>
> HTH,
> Arthur
> _______________________________________________
> SG14 mailing list
> SG14_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/sg14
>
Received on 2024-03-01 23:39:34