[HN Gopher] Try to make sudo less vulnerable to Rowhammer attacks
___________________________________________________________________
Try to make sudo less vulnerable to Rowhammer attacks
Author : trebligdivad
Score : 120 points
Date : 2024-01-28 13:09 UTC (9 hours ago)
(HTM) web link (github.com)
(TXT) w3m dump (github.com)
| trebligdivad wrote:
| Having to code like this everywhere would be hell!
|
| Scroll to the top for the reference to the Mayhem attack it's
| trying to guard against.
| hlandau wrote:
| This is deeply interesting.
|
| I've sometimes contemplated the possibility of doing things like
| this to guard against memory errors causing mis-entry to
| particularly critical control flow paths - this is certainly an
| example of that. But never heard of anyone actually trying to do
| this until now.
|
| A "how to write rowhammer-resistant code" writeup would
| definitely be useful here - even if it is definitely something
| people cannot do for anything, I can certainly see cases where
| there is a case for it.
| Palomides wrote:
| it's a long-known hazard in embedded and highly reliable
| systems, there are terms like "single-event upset" that might
| lead you in interesting directions
| addaon wrote:
| Single event upsets are well-understood and easy (although
| not necessarily cheap) to mitigate in hardware -- ECC for
| RAM, CRCs for data in flight, and voting (or lockstep if
| detection is sufficeint) for computation. The challenge with
| Rowhammer is that it involves multiple, correlated bit flips;
| and depending on details of your system the correlations may
| be disguised by various remapping layers.
| wongarsu wrote:
| I remember someone making a rust library for hardened bools,
| though with the idea of protecting against protecting against
| random bitflips, not targeted rowhammer attacks (though it
| should work about the same).
|
| The feedback from the rust subreddit was basically protecting
| the bool but not the if statement is of limited use.
| Potentially it can even make it worse, since there is now more
| code being executed that might become bitflipped.
|
| That inspired me to make a crate that periodically checksums
| your program code while it's running, to make sure it hasn't
| changed. Got it working on Windows and Linux, but then it ended
| up like most side projects. Maybe I should polish it up and
| publish it.
| 4gotunameagain wrote:
| Seems like an interesting compiler level protection, a decorator
| for enums so that they are compiled to values with maximum
| hamming distance between them.
| rcthompson wrote:
| Couldn't compilers be configured to use such values for for any
| enum type? And maybe even auto-insert the appropriate check in
| the final unchecked else anywhere that enum type is otherwise
| exhaustively checked?
| f_devd wrote:
| Yes it's possible, but it's not desirable. It wouldn't be
| backwards compatible, and not safe for shared libraries. It's
| better suited for a linter-type error/warning.
| andrewaylett wrote:
| It's not plausible in C, for the reasons you mention, but it
| _might_ be more possible in other languages -- Rust, for
| example, only guarantees specific representations when
| instructed and doesn 't allow for shared libraries without a
| specified representation, so it wouldn't have either issue
| for most application code. Dynamically-typed languages
| similarly should be able to choose enum values at runtime in
| many cases.
| rcthompson wrote:
| > Dynamically-typed languages similarly should be able to
| choose enum values at runtime in many cases.
|
| I wonder, is choosing random enum values at runtime more
| secure against Rowhammer than just having fixed values that
| were chosen randomly once and compiled in, since presumably
| the attacking code now has no way to know which bits it
| needs to flip? If so, it might even be desirable to
| implement this as a "secure enum" in a compiled language.
| mcculley wrote:
| From the commit: "The values used were chosen such that
| it takes a large number of bit flips to change from
| allowed to denied. Using random values doesn't really
| protect against this attack."
|
| It would be neat to see an algorithm that generates
| suitable values.
| f_devd wrote:
| The basic algorithm for the 2-enum case from the commit
| seems to just be `enum { A = rand(), B = ~A}`.
|
| Although I'm not sure if it's optimal, the many case
| seems to be the same as the 2-case but repeated for every
| 2 items. I expect they double checked that the amount of
| bitflips is still pretty high.
|
| Maybe a better algorithm for the many case would be
| something like the popcnt parallel patterns:
|
| * 0b0101010101010101
|
| * 0b0011001100110011
|
| * 0b0000111100001111
|
| * 0b0000000011111111
|
| Since they would all have equal hamming distance between
| each of the entries.
| mcculley wrote:
| Yeah, I was specifically wondering about n>2. Your
| approach seems reasonable.
| tomsmeding wrote:
| The n=2 case also occurs in the commit:
| https://github.com/sudo-
| project/sudo/commit/7873f8334c8d3103...
|
| And indeed, the two values ate bitwise complements.
| loeg wrote:
| The problem with C and C++ is that enum values are explicitly
| incrementing, even when the user does not specify a literal
| value for each one. So if anything depends on a specific value
| (e.g., disk or network formats), this will break it. I think
| Rust enum values are similar, but I'm not a language expert.
| fl0ki wrote:
| Rust enums are incrementing too so that they can generate
| machine code with dense jump tables. If you also want the
| discriminant value for something, you opt into that with e.g.
| #[repr(u8)] and you can even override the values. Note that
| unlike in many other languages, casting from a number to the
| enum is a falliable operation because not all values are
| valid.
|
| Making something like this into a panic is not a good fit for
| Rust as-is. Because enums are proven to have only correct
| values, not only is code written to assume pattern matches
| cannot panic, but compilers are free to optimize around only
| having valid values as well. That goes not only for the enum
| discriminant, but for any associated values being properly
| initialized values of their respective types.
|
| In a sense, Rust lets you write code as if invalid values
| never happen, so there's less to check for in your code. It's
| understandable from the perspective of the abstraction needed
| for computer code to be "correct" and not just temporarily
| getting away with Undefined Behavior. There are simpler ways
| to violate it than just rowhammer, write straight to process
| memory for example, which can also violate invariants that
| compilers assumed while optimizing.
|
| If you wanted to compile Rust (or anything else) with a
| hardening mode that does check what should be redundant
| values, it would be a lot slower and code that never panicked
| before would now panic, but it would probably be a worthwhile
| tradeoff for some programs to opt into. After all, if you
| built for CHERI or arm64e and got a machine exception from an
| unauthenticated pointer, you'd be thrilled you mitigated a
| vulnerability even if it violated your higher-level language
| model. Defense in depth and all that.
|
| Maybe someone feels motivated enough to write an RFC and
| prototype for this. It just wouldn't stop at enum values, it
| should mean all sorts of other things too, such as not
| eliding any other checks that appear redundant given
| assumptions like immutability. That's what makes it slow and
| hard to reason about.
| kazinator wrote:
| Yes; gcc and clang could, in principle, support an extension
| like: __attribute__((rand)) enum e { FOO, BAR,
| ... };
|
| which randomizes the values, as an extension.
|
| You only need this in specific places, like setuid programs.
|
| Randomization can be bad because it wrecks build
| reproducibility; it would have to be tied to the GNU Build ID.
|
| If such an enum is used in any interface between files, the
| randomization has to be the same in every translation unit.
|
| Maybe the syntax could specify a seed: rand(42).
| nraynaud wrote:
| Interesting it's the exact opposite of Gray code's goal, I
| suppose this has been studied, with fixed size words maximal
| distance problems are tractable.
| defrost wrote:
| This wikipedia article must surely be inaccurate:
|
| https://en.wikipedia.org/wiki/Row_hammer The
| initial research into the row hammer effect, published in June
| 2014, described the nature of disturbance errors and indicated
| the potential for constructing an attack, but did not provide any
| examples of a working security exploit. [1]
|
| [1] (June 24, 2014). "Flipping Bits in Memory Without Accessing
| Them: An Experimental Study of DRAM Disturbance Errors"
|
| By my recollection there was a discussion of rowhammer and making
| it work on a (original) Freenode channel circa 2010 (or earlier)
| in response to a related thread on a reddit security hacking
| subreddit.
|
| ie: it was being discussed in public channels some four years
| prior to a paper cited as "initial research".
|
| Addendum: Mind you, lots of things get kicked about and
| implemented before actual papers appear on them for the first
| time in public.
| rcthompson wrote:
| Well, that quote from Wikipedia says the published paper didn't
| include a working exploit. That might be true even if a working
| exploit was available after the paper was written and submitted
| but before it was published. (I don't know if this is the case
| here, but this sort of thing is common in scientific
| publishing.)
| xgk wrote:
| The possibility of flipping bits in DRAM in a Rowhammer like
| fashion, was known in the DRAM industry since at least the
| 1990s (sorry, no reference handy), and Rowhammer-like access
| was used in DRAM quality testing.
|
| As silicon density increased, the issue became more urgent.
| TheAdamist wrote:
| Is there any reason for the void cast here? Theres no return
| value in use.
|
| (void)strlcpy(des_pass, pass,sizeof(des_pass));
| mandarax8 wrote:
| strlcpy returns a size_t, so just to silence the discarded
| return value warning
|
| https://linux.die.net/man/3/strlcpy
| azinman2 wrote:
| I had the same q! Perhaps a comment would have helped given
| the context for the PR.
| loeg wrote:
| Sure; it's just irrelevant to the diff's logical change.
| formerly_proven wrote:
| tfw hardware becoming so unreliable people start using 32-bit
| maximum distance codes for enumerations with like four values.
| foota wrote:
| Feels like a language with opaque enums and pattern matching
| could implement this kind of thing behind the scenes.
| kazinator wrote:
| Once you assign the values in the C enum, a switch statement is
| "opaque".
|
| Just inefficient; a jump table optimization is impossible on
| the values. Speed is sacrificed for security. A jump table
| itself could be row-hammered to jump where the attacker wants!
| HankB99 wrote:
| I thought that Rowhammer was a thing of the past. Out of
| curiosity I found code to test for this and ran it on some of my
| hosts. My old desktop - I7-4770K/DDR3 - was susceptible. My old
| server - Xeon X3460/DDR3+ECC - was not. I upgraded the desktop
| with components based on a Ryzen 7 7700X/DDR5. It tested not
| susceptible. I'm not sure if that's a result of RAM designed not
| to be susceptible or that (I think) DDR5 RAM uses modules with on
| die ECC.
|
| I was not able to test any of my Raspberry Pis because the test
| code used some facility available on the AMD64 architecture that
| is not available on the ARM64 processors. The newer Pi 4B and 5
| use modules with on die ECC.
|
| It seems to me that ECC should prevent Rowhammer susceptibility.
| That should prevent it on server grade H/W for anything still in
| service and newer consumer systems.
|
| I have no idea if Rowhammer affects other architectures than
| AMD64.
| trebligdivad wrote:
| Rowhammer is not architecture specific, since it's the DRAM
| rather than the CPU.
|
| The paper linked in the patch references other works showing
| every defence at rowhammer can be bypassed somehow (I've not
| followed them all) - e.g. it specifically says that ECC and the
| like can be bypassed.
| HankB99 wrote:
| Interesting. I did not expect that Rowhammer was architecture
| specific, only that the test I found was.
|
| I also did not expect that the various defenses, including
| ECC, could be bypassed.
| adrian_b wrote:
| It cannot be completely bypassed.
|
| The attacker cannot control precisely which bits will be
| erroneous. When much more than 2 bits become erroneous, in
| a small fraction of the cases no error will be detected but
| a wrong value will be read at the next access.
|
| However, in the majority of the cases an error will be
| detected, either non-correctable, or correctable in which
| case the corrected value will be wrong.
|
| Despite the fact that wrong corrections are possible, in a
| system with ECC that is configured correctly it should be
| impossible for a RowHammer attack to escape detection,
| unlike for a system without ECC memory.
|
| On a computer that is not defective, memory errors happen
| very seldom, typically one error after many months. Even
| only 2 correctable errors that happen in the same day
| represent an event that can be explained only by either a
| RowHammer attack or by a memory module that has become
| defective.
|
| Therefore, a well configured computer with ECC memory
| should alert immediately its administrator when 2 on more
| errors happen in the same day, even if they had been
| correctable errors, because this requires immediate action,
| either stopping a RowHammer attack or replacing the
| defective memory module.
|
| It would be pretty much impossible for any RowHammer attack
| to attain its target without triggering 2 or more ECC
| errors, which will reveal the attack attempt.
|
| Only when there is no ECC the attack can proceed undetected
| for a time long enough to be successful.
| thijsr wrote:
| RowHammer is not a thing of the past. In fact, modern DRAM
| chips are significantly more susceptible to RowHammer due to
| their increased chip density [1].
|
| [1] https://arxiv.org/abs/2005.13121
| HankB99 wrote:
| Thanks for the link. I guess I thought wrong. But I have more
| questions.
|
| > with RowHammer protection mechanisms disabled
|
| I wonder what this means. Is it S/W mitigations or does it
| include H/W factors like disabling on-die ECC.
|
| It makes sense to me that with all other things being equal
| that higher density would lead to more susceptibility to
| Rowhammer. But as always, other things are not equal. I
| expect that on-die ECC would reduce susceptibility to
| Rowhammer and AFAIK that is used for DDR4 and DDR5 RAM, but
| perhaps not exclusively. Or did disabling "protection
| mechanisms" include disabling that (if it is even possible.)
| omoikane wrote:
| In the "countermeasures" section of the linked paper[1], it
| mentioned that there are some new techniques available, but
| repeatedly mentioned that they are not yet available in
| consumer systems. Maybe rowhammer will eventually be a thing
| of the past despite the increasing chip density.
|
| [1] https://arxiv.org/abs/2309.02545
| tlb wrote:
| LPDDR4 and above are supposed to have a feature to detect too
| many accesses to the same few rows and initiate a refresh
| cycle. Implementation quality may vary.
| xgk wrote:
| Have you ever seen any even moderately detailed specification
| of what the DRAM manufacturers do in this regard? I have not,
| and I looked. I am deeply sceptical ....
|
| I don't believe that Rowhammer mitigations happen inside the
| DRAM chips themselves, I think that they are being put into
| the memory controller that talks to DRAM. Since DRAMs with
| built-in Rowhammer defences would have to spend transistors
| on this defence, those transistors would be 'wasted' in
| situations where Rowhammer is not part of the attacker model.
| tlb wrote:
| It makes sense to put it in the DRAM controller for many
| reasons. One is that the DRAM silicon process is optimized
| for memory but terrible for logic. Also, a DRAM bank is
| several chips in parallel to get the data bus width, and
| they would all have to duplicate the logic.
|
| The disadvantage is that the controller and memory are made
| by different companies, so standards are required to agree
| on what access patterns are acceptable.
| xgk wrote:
| Agree. The extreme secrecy of DRAM manufacturers about
| the innards of their chips puts an additional obstacles
| in the way of memory controllers (MCs) implementing
| efficient Rowhammer defences. In particular, if the MC
| doesn't know which addresses are corresponding to
| neighbouring rows, how can an MC know with certainty that
| any concrete row is being attacked? (And, to the best of
| my knowledge, DRAM manufacturers don't give away this
| information.)
| xgk wrote:
| It has been possible to re-purpose such additional refresh
| cycles as an additional Rowhammer attack vector, see https://
| www.usenix.org/conference/usenixsecurity22/presentat...
| dist-epoch wrote:
| Are you running the Ryzen DDR5 at stock speeds (4800 MT/s) or
| at some XMP profile.
| HankB99 wrote:
| Everything is at stock speed.
| jareklupinski wrote:
| i enjoyed this part: #define AUTH_SUCCESS
| 0x52a2925 /\* 0101001010100010100100100101 */ #define
| AUTH_FAILURE 0xad5d6da /* 1010110101011101011011011010 */
| #define AUTH_INTR 0x69d61fc8 /* 1101001110101100001111111001000
| */ #define AUTH_ERROR 0x1629e037 /*
| 0010110001010011110000000110111 */ #define
| AUTH_NONINTERACTIVE 0x1fc8d3ac /* 11111110010001101001110101100
| \*/
|
| going to see how i can work this into a project :)
| trebligdivad wrote:
| That's the section that made me post this snippet; crazy isn't
| it?!
| jareklupinski wrote:
| can't edit original post, but i just realized after lining up
| the monospace how #define AUTH_SUCCESS
| 0x52a2925 /* 0101001010100010100100100101 */
| #define AUTH_FAILURE 0xad5d6da /*
| 1010110101011101011011011010 */ #define AUTH_INTR
| 0x69d61fc8 /* 1101001110101100001111111001000 */
| #define AUTH_ERROR 0x1629e037 /*
| 0010110001010011110000000110111 */ #define
| AUTH_NONINTERACTIVE 0x1fc8d3ac /*
| 11111110010001101001110101100 */
|
| AUTH_FAILURE is still just !AUTH_SUCCESS (and almost a
| palindrome)
| throwaway421967 wrote:
| I don't get it, what's special about these numbers?
| jareklupinski wrote:
| i think their bitfields seem specifically chosen to mitigate
| rowhammer attacks (no repeating elements)?
| rsaxvc wrote:
| Takes many bit flips to go from one pattern to another.
| throwaway421967 wrote:
| If that is the only constraint, wouldn't the goal be to be
| as far as possible from the only success state?
|
| the distance between success and failure is 28
| TacticalCoder wrote:
| > I enjoyed this part
|
| Very nice indeed. Such a simple mitigation and it makes evil
| people sad, which makes me happy.
| dist-epoch wrote:
| This is for local sudo privilege escalation.
|
| If the attacker is already running code on your system, you
| kind of lost anyway.
| nuccy wrote:
| Not really. An example out of top of my head, where this
| still might be useful are login nodes (used in many
| research clusters to allow users to enter and sumbit jobs)
| or shared web-hosting servers (few of those definitely
| still exist). There legitimate non-privileged users can run
| their programs and the end goal is to prevent them from
| getting root.
| charcircuit wrote:
| Another common one is for example minecraft / source
| engine game server hosts as they commonly allow customers
| to install mods.
| wongarsu wrote:
| The last time I looked at the statistics the majority of
| the internet was still running on PHP, mostly wordpress
| installs. I'm willing to bet those are mostly on shared
| hosting with accounts separated by nothing but their
| linux user.
| stressinduktion wrote:
| Related, hardbool, it seems gcc can automatically handle this
| soon.
|
| https://blog.adacore.com/adacore-enhances-gcc-security-with-...
| and https://gcc.gnu.org/onlinedocs/gcc/Common-Type-
| Attributes.ht...
| loeg wrote:
| Not exactly; these constants in sudo are an enum of sorts
| (actually preprocessor macros). It's not just bool (and won't
| just be bool in many situations). It is cool to see GCC
| exploring automatic protection in this space; I just don't
| think it is relevant to what sudo did here.
| IshKebab wrote:
| Hardbool lets you use custom true and false representations
| with higher hamming distances. The sudo patch uses custom
| representations for their enum that have higher hamming
| distances. The only difference is that hardbool is for
| true/false and this patch is for
| AUTH_SUCCESS/AUTH_FAILURE/AUTH_ERROR etc. But that's
| irrelevant. It's the exact same technique.
| loeg wrote:
| > The only difference is that hardbool is for true/false
| and this patch is for AUTH_SUCCESS/AUTH_FAILURE/AUTH_ERROR
| etc. But that's irrelevant.
|
| It's very relevant! The problematic comparison in this code
| isn't true/false! A feature that only protects true/false
| does not help here.
| jesprenj wrote:
| Good job, though it doesn't really help when you have a buffer
| overflow leading to RCE in a suid binary (remember sudoedit CVE?)
| dist-epoch wrote:
| Even without an explicit attack, today's memory is pretty
| fragile, I'm regularly seeing bit flips.
| PrimeMcFly wrote:
| > I'm regularly seeing bit flips.
|
| How?
| dist-epoch wrote:
| For example 7zip decompression CRC failures that resolve on a
| second try. It would be longer to explain how, but I tracked
| it multiple times to a single bit flip in the decompressed
| output.
| asah wrote:
| I wonder how this might work with bit-dense systems (e.g.
| databases like PostgreSQL) where every bit has meaning and it's
| wildly impractical to reassign bitpatterns like this.
| kzrdude wrote:
| When do these values get into memory, shouldn't they be in
| registers generally? Maybe it's when the program loaded to memory
| to execute, but is that part rowhammerable?
| frozenport wrote:
| Really struggle to understand the thread from Rowhammer.
|
| It would seem to be particularly dangerous, but then I don't see
| everybody getting pwned.
| kazinator wrote:
| Someone in a comment suggested ...
|
| > _gcc -DRND1=0x$(openssl rand -hex 4) ..._
|
| That would cause grief to reproducible distro initiatives.
|
| It is perfectly good enough for the error code enumeration to be
| statically randomized into hard coded constants. The attacker is
| very unlikely to flip every single bit of one valid value so that
| it resembles another valid value.
|
| Even if the values were randomized at compile time, if the
| executable is readable to the attacker, the attacker can learn
| what those values are.
|
| If the executable is not readable to the attacker, the attacker
| can just pull a copy of the executable from the distro package:
| executables are installed from widely used binary packages, not
| freshly compiled for every system.
| thulle wrote:
| > It is perfectly good enough for the error code enumeration to
| be statically randomized into hard coded constants.
|
| A comment points out that they aren't randomized:
|
| > The values used were chosen such that it takes a large number
| of bit flips to change from allowed to denied. Using random
| values doesn't really protect against this attack.
| SkyMarshal wrote:
| Does anyone have any opinions on doas vs sudo? I've heard doas
| recommended as being more minimalistic, and various advantages
| that brings. What are the pros and cons between the two?
| charcircuit wrote:
| My opinion is to have neither. Requiring users to switch to an
| account that has different privileges is evidence of poor
| design of the operating system. Having a root user who has full
| privileges over the entire system is also poor design as it is
| the opposite of the principle of least privilege. If a user has
| the privilege to do something they should be able to do it with
| their normal account.
| forty wrote:
| I'm happy that my mistakenly typed rm -rf / fail when I use
| my non root user (normal account) even though I also have
| root for when I do want to mess things up.
| pid1wow wrote:
| Given that the most common use of sudo is to give yourself root
| to run a command, and malware looking to elevate root can just
| rig up ~/.bashrc, what use is this patch? What use cases does it
| apply to and how common are they?
| wongarsu wrote:
| Sudo has much more fine-grained abilities for more surgical
| use-cases, like giving users the ability to only execute
| certain commands as a certain user, with detailed logging and
| auditing. It has a pretty involved config file (the pdf docu
| for it is 80 pages long), a plugin system, a seperate log
| format and log server, etc
|
| I also believe those use-cases aren't that common anymore since
| multi-user systems fell out of favor. There is an argument that
| most of us could use a vastly simpler tool instead to reduce
| the attack surface. But that tool wouldn't be sudo, because
| sudo is built around supporting all these use cases.
| bpye wrote:
| doas [0, 1] in OpenBSD is somewhat simpler.
|
| [0] - https://man.openbsd.org/doas.1
|
| [1] - https://man.openbsd.org/doas.conf.5
| _visgean wrote:
| Haha I have done exactly that as a joke in highschool
| https://github.com/Visgean/fakesudo
| cedws wrote:
| >and malware looking to elevate root can just rig up ~/.bashrc,
| what use is this patch?
|
| Apologies for self promotion, but I wrote a relevant blog post
| that discusses this[0]. Is there any way of mitigating this
| trivial attack?
|
| I feel like the Unix/Linux security model is broken.
|
| [0]: https://cedwards.xyz/sudo-is-broken/
___________________________________________________________________
(page generated 2024-01-28 23:00 UTC)