• lobsters__olliej@crustaceanson.theshelf.topOPM
      link
      fedilink
      arrow-up
      1
      ·
      12 days ago

      memset_s serves that purpose, but this is the specific problem being discussed in the article: if the value is not in memory you don’t need to zero the memory, but by calling memset[_s] you force the compiler to spill the value onto the stack for the express purpose of zeroing it.


      score 1 · canonical

      • Unless the compiler handles calls to memset_s() specially so it “knows” to zero out the variable, regardless of where it lives (memory or registers). C compilers already can treat memset() (and memcpy(), memmove()) specially (when you include <string.h>) by inlining them or calling a function, depending upon the context.


        score 1 · canonical

        • lobsters__olliej@crustaceanson.theshelf.topOPM
          link
          fedilink
          arrow-up
          1
          ·
          11 days ago

          The compiler knows the semantics of memset, specifically that it can be treated as a dead store. memset_s (and the other _s functions) have defined semantics of guaranteeing a write, even if it’s dead - a compiler that tried lowering them such that they did not zero the memory would be nonconforming. As a result they essentially just don’t special case them at all.


          score 1 · canonical

            • lobsters__olliej@crustaceanson.theshelf.topOPM
              link
              fedilink
              arrow-up
              1
              ·
              11 days ago

              memset_s: more or less yes, that is the core of what this article is talking about.

              It sounds like memset_explicit might be the “solution”. This requires the backend be aware of these semantics, and while I don’t know about GCC, llvm’s register allocation does not carry such info and I’ve generally encountered push back against adding the data required to track such semantics (though in my case in the context of pointer authentication). IMO it would be a general win for a lot of security related purposes.


              score 1 · canonical

      • The right answer should be memset_explicit(). I was disappointed that this article never mentioned the Standard solution to this issue, or pre-standard versions such as explicit_bzero(). But I don’t think that omission makes much difference because clang and gcc don’t seem to know enough about memset_explicit() to avoid a spill to memory.

        Unfortunately memset_s() is entangled with the Annex K portability problems and its crazy runtime constraint checking and exception handling, which makes it hard for compilers to implement memset_s() as a sensible builtin.

        The special case for for memset_s() is:

        Unlike memset, any call to the memset_s function shall be evaluated strictly according to the rules of the abstract machine as described in 5.1.2.4. That is, any call to the memset_s function shall assume that the memory indicated by s and n may be accessible in the future and thus contains the values indicated by c.

        And for memset_explicit():

        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 (7.26.6.1).)


        score 1 · canonical

    • As the essay shows in spades the zeroing itself, when it does happen, can cause more harm than good by forcing stack storage or even extra copies. The problem is that the intent is not conveyed to the compiler, so you’re still playing silly bugger with it, not optimizing the wrong operation still does not do the right thing.

      As Simon, Chisnall, and Anderson noted near 10 years ago in “what you get is what you C”, you can’t solve this by trying to trick the compiler for eternity, it would be a lot more sensible to tell the compiler what you need clearly.


      score 1 · canonical