Date: Sat, 2 Mar 2024 09:48:44 -0800
James,
> I rarely jump into these discussions...
I am glad you and others did. Well said response. I suggested
safe_memory_alloc() on the forum in order to get feedback. I appreciate
the helpful suggestions and constructive criticism I've received from
everyone.
Robin Rowe
Beverly Hills, California
*Chairman ISO WG21 SG14 C++ Banking and Financial Systems Subcommittee
On 3/1/2024 3:39 PM, James Mitchell wrote:
> 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] <mailto:sg14_at_[hidden]>> wrote:
>
> On Fri, Mar 1, 2024 at 3:10 PM Robin Rowe via SG14
> <sg14_at_[hidden] <mailto: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] <mailto:SG14_at_[hidden]>
> https://lists.isocpp.org/mailman/listinfo.cgi/sg14
> <https://lists.isocpp.org/mailman/listinfo.cgi/sg14>
>
> I rarely jump into these discussions...
I am glad you and others did. Well said response. I suggested
safe_memory_alloc() on the forum in order to get feedback. I appreciate
the helpful suggestions and constructive criticism I've received from
everyone.
Robin Rowe
Beverly Hills, California
*Chairman ISO WG21 SG14 C++ Banking and Financial Systems Subcommittee
On 3/1/2024 3:39 PM, James Mitchell wrote:
> 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] <mailto:sg14_at_[hidden]>> wrote:
>
> On Fri, Mar 1, 2024 at 3:10 PM Robin Rowe via SG14
> <sg14_at_[hidden] <mailto: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] <mailto:SG14_at_[hidden]>
> https://lists.isocpp.org/mailman/listinfo.cgi/sg14
> <https://lists.isocpp.org/mailman/listinfo.cgi/sg14>
>
Received on 2024-03-02 22:35:10