[HN Gopher] New Linux glibc flaw lets attackers get root on majo...
       ___________________________________________________________________
        
       New Linux glibc flaw lets attackers get root on major distros
        
       Author : PaulHoule
       Score  : 134 points
       Date   : 2024-02-04 13:35 UTC (9 hours ago)
        
 (HTM) web link (www.bleepingcomputer.com)
 (TXT) w3m dump (www.bleepingcomputer.com)
        
       | tomsmeding wrote:
       | Duplicate, more discussion here:
       | https://news.ycombinator.com/item?id=39194093
        
         | wslh wrote:
         | I have not found Rust mentioned there. Am I right if we say
         | that using Rust will reduce the security attack surface in
         | Linux? I see now it is mentioned in this thread and there are
         | different views about this.
        
           | tialaramex wrote:
           | The obvious ways to do this in Rust are safe, on the other
           | hand, if you write some Rust where all it does is call this
           | glibc function then Rust didn't magically save you because
           | the function is broken. Does that help?
        
           | smolder wrote:
           | Being that it is a buffer overflow, yes, if glibc were in
           | Rust I think it shouldn't have happened. I believe I saw a
           | libc reimplementation project in Rust, but that's a big
           | effort and not something I would expect to be drop-in for a
           | long time. That said, I think we should try to avoid turning
           | every vulnerability discussion into one about the merits of
           | Rewriting it in Rust.
        
           | shirro wrote:
           | Memory safety isn't exclusive to Rust. Language designers and
           | implementers solved these problems years ago with safer
           | languages (better type systems, removing pointers) or safer
           | runtimes (jvm, .net etc). Rust is notable because it brings a
           | lot of prior art together in one place and the compiler
           | prevents most problems at compile time. As long as
           | programmers don't use unsafe Rust it can produce safer code
           | without runtime overhead. There are a lot of safer languages
           | than C. Not all are suitable for writing system libraries.
        
       | cutler wrote:
       | Just curious whether Rust would have averted these issues?
        
         | voxl wrote:
         | The answer is probably yes. The issue is caused by a heap-
         | memory overflow. In Rust, if you're storing an array on the
         | heap then there will be bounds checks on the array access. If
         | you're using a vec then it would automatically grow (so again
         | bounds checks).
        
           | ape4 wrote:
           | It would be quite something to rewrite glibc in Rust! Of
           | course, its the C/C++ run time.
        
             | yjftsjthsd-h wrote:
             | It's a funny thought, but I don't think there's any reason
             | it shouldn't work; IIRC redoxos is providing a libc written
             | in rust for compatibility.
        
               | Retr0id wrote:
               | The redox impl is here: https://github.com/redox-
               | os/relibc
               | 
               | Apparently it also supports linux!
        
             | Retr0id wrote:
             | It would be quite amusing, but there's no reason in
             | principle that it couldn't be done.
             | 
             | The user-facing APIs would be just as dangerous as ever,
             | but the internal implementation-detail stuff - like this
             | "__vsyslog_internal" - could presumably be made safer.
        
             | pjmlp wrote:
             | As C++ advocate on C vs C++ flamewars, I already find quite
             | ironic that GCC, clang, clang's libc and Windows Universal
             | C Runtime are all written in C++.
             | 
             | All those C users hatting on C++, while being forced to use
             | tooling that has dropped C for C++.
        
               | tmtvl wrote:
               | > _All those C users_ hatting _on C++_
               | 
               | Such haberdashery!
               | 
               | Anyway, something that I appreciate about the Rust
               | community is their dedication to rewriting anything and
               | everything in Rust. I'd love it if the Lisp community
               | also had some of that drive. Seeing a library which uses
               | a Python script to generate documentation is so
               | disheartening, they don't need that horrendous, awful
               | garbage, they could just do it in Lisp.
        
               | gnabgib wrote:
               | I can't quite guess what you meant (balderdash[1]?), but
               | I'm pretty sure you didn't mean a goods and wears shop[0]
               | 
               | [0]: https://www.merriam-
               | webster.com/dictionary/haberdashery [1]:
               | https://www.merriam-webster.com/dictionary/balderdash
        
               | uecker wrote:
               | Indeed it is a shame. All bloated. It would be nice to
               | have small and lean tool chain again.
        
               | flohofwoe wrote:
               | You're not exactly "forced" to use the C stdlib though.
               | 
               | That's one nice thing about C, that the C stdlib is so
               | bare bones that it could just as well not exist and not
               | much would be lost (much harder to ignore the stdlib in
               | C++, at least for some "modern C++" features).
               | 
               | Besides: MUSL is written in plain C ;P
        
               | pjmlp wrote:
               | Yeah, but that isn't C any longer, it is a flavour of a
               | language that largely looks like C.
               | 
               | C is the complete printout from ISO/IEC 9899.
        
             | nindalf wrote:
             | Yeah it would. There are a few attempts, such as C-gull (ht
             | tps://github.com/sunfishcode/c-ward/tree/main/c-gull#readm.
             | ..).
             | 
             | > c-gull is a libc implementation. It is an implementation
             | of the ABI described by the libc crate.
             | 
             | > Currently it only supports _-_ -linux-gnu ABIs, though
             | other ABIs could be added in the future. And currently this
             | mostly focused on features needed by Rust programs, so it
             | doesn't have all the C-idiomatic things like qsort yet, but
             | they could be added in the future.
        
             | flohofwoe wrote:
             | It's not so unusual to write the C stdlib in a different
             | language.
             | 
             | E.g. Zig is getting a libc written in Zig:
             | 
             | https://github.com/ziglang/zig/issues/514
             | 
             | Rust would work too of course. MUSL is probably the only
             | popular C stdlib actually written in C.
        
               | 2OEH8eoCRo0 wrote:
               | I think their point is that the c in glibc stands for C,
               | as in the language. A glibc rewrite in Rust wouldn't be
               | glibc anymore!
        
               | wongarsu wrote:
               | Well, the Common Language Runtime that powers .Net
               | languages isn't written with .Net, and the Java runtime
               | environment isn't written in Java.
               | 
               | I know these are a lot more involved than the functions
               | provided by the glibc, but the point is that libc means
               | Library _for_ C, not Library _in_ C.
               | 
               | And of course on Unixes all software is expected to link
               | to the glibc for basic functionality, no matter whether
               | it's written in C or not. So the name is only
               | historically accurate.
        
               | wrs wrote:
               | Yes, but even more so. Linux "expects" programs to link
               | to libc, but many other Unix-like OSes _require_ programs
               | - in _any_ language -- to link to libc. The kernel
               | syscall interface is not considered a stable API.
               | 
               | Windows has a similar structure, but the required kernel
               | interface library is not the same one as the C runtime,
               | so it's less confusing language-wise.
        
               | pjmlp wrote:
               | > Well, the Common Language Runtime that powers .Net
               | languages isn't written with .Net, and the Java runtime
               | environment isn't written in Java.
               | 
               | Actually a large part of it is, and in Java's case there
               | are bootstraped implementations, OpenJDK isn't the only
               | one.
               | 
               | Java is like C and C++, where multiple implementations
               | are available for a given specification.
               | 
               | https://docs.oracle.com/javase/specs/
        
           | loeg wrote:
           | Yeah, unless you bypassed the bounds checking with unsafe{}
           | for performance reasons. (The is an unlikely place to do so.)
        
           | uecker wrote:
           | There was a similar bug in the past in Rust: Integer overflow
           | causing a buffer overflow: https://github.com/rust-
           | lang/rust/pull/54399/commits/8ac88d3...
           | 
           | So the answer is: Not necessarily. I agree though that the
           | memory safety features in Rust would help reduce the risk. On
           | the other hand, one could also write safer C by abstracting
           | away buffer management. The world is not black and white.
        
             | e44858 wrote:
             | That function was using an unsafe block to disable the
             | bounds check. This kind of bug would likely be impossible
             | in "safe" rust (that didn't use unsafe).
        
               | ajross wrote:
               | The linked glibc code was likewise being fancy trying to
               | use a stack buffer to avoid a heap allocation. Idiomatic
               | C would have worked just fine too. The point is that Rust
               | is also (though not as severely) subject to developers
               | playing tricks and hurting themselves.
        
               | uecker wrote:
               | In reality people use unsafe to optimize and then
               | introduce bugs. You could also use a safe abstractions to
               | avoid bounds violations in C.
        
           | ajross wrote:
           | It's actually questionable. The vulnerable code is described
           | really well in the Qualys report:
           | https://www.qualys.com/2024/01/30/cve-2023-6246/syslog.txt
           | 
           | Basically: the code was an optimization trying to avoid a
           | heap allocation by using a stack buffer instead, but its
           | fallback for "it doesn't fit in 1k of stack memory" wasn't
           | tested and didn't work right.
           | 
           | Rust struggles with alloca-style code too, to the extent that
           | users who want to do this kind of trickery usually resort to
           | unsafe. There's a link elsewhere in this topic to a Rust
           | library vulnerability that looks similar.
           | 
           | The upshot is that if the code had been written idiomatically
           | and just used the heap like it was supposed to, it probably
           | would have worked fine. The glibc authors got fancy, loaded a
           | footgun, and it went off, something that Rust is equally
           | capable of.
        
             | deathanatos wrote:
             | > _Rust struggles with alloca-style code too, to the extent
             | that users who want to do this kind of trickery usually
             | resort to unsafe._
             | 
             | Sort of, but not really. I've done this sort of "if it fits
             | in a small stack buffer, use that, else fall back to heap"
             | in code too. It's possible in safe-ish Rust:
             | let mut s = SmallString::<[u8; 1024]>::new();
             | write!(s, "{} frobs for {} widgets.", var_a, var_b)
             | .expect("writes to a String are infallible");
             | 
             | Safe-ish, because there is an unsafe {} hidden behind the
             | safe interface exposed by `SmallString`. (And that crate
             | _has_ had vulnerabilities against it.)
             | 
             | But that's sort of the point: we're here looking at logging
             | code, and the logging code itself shouldn't be doing this
             | sort of unsafe trickery, it should be using some
             | interface/utility that handles that, and then there's only
             | a single spot that needs safety analysis. (Here, that would
             | be the smallvec crate.) While I don't think C outright
             | prevents you from building an equivalent, certainly how
             | people tend to approach C does, and we see that here. The
             | pattern is unsafe at the instantiation site, not at the
             | definition, leading to far more unsafe code. (Not to
             | mention the fact that in C, we have no unsafe {} blocks, so
             | one must assume _all_ such code is unsafe, but even if we
             | just consider the laughable and impossible-to-grep  "just
             | the actually unsafe parts", it's far, far more.)
             | 
             | In the C at hand here, the second attempt here fails due to
             | a variable never _truly_ getting initialized (in the sense
             | of having a meaningful value), and the version prior to
             | that is just _laughably_ unsafe, allocating a 1 byte buffer
             | but passing a size far larger.
             | 
             | (The second attempt (i.e., time of bug) also uses the
             | cursed variable name `l`, and the site this code is hosted
             | on uses a font where `l` and `1` are homoglyphs.)
        
         | foobiekr wrote:
         | There's a kind of naive thing going on on HN where peopel see a
         | bug and ask whether or assert Rust will fix it. The problem is
         | they are homing in on just the bugs Rust addresses - this is
         | fine, I've had this conversation at work (not involving Rust,
         | even) for two decades, including discussions about Java.
         | 
         | The problem is that Rust solves _a_ class of issues without
         | even beginning to address other kinds of problems, and so this
         | sort of thinking is evolving toward a mindset that "oh it's
         | rust so no security issues", which is completely wrong.
        
           | eichin wrote:
           | Perhaps an innoculation against this is to respond "Yes, but
           | so would Ada"
        
             | pjmlp wrote:
             | I keep using "mostly safe languages" as kind of abstract
             | term, Rust comes into speech even in scenarios where other
             | alternatives would be a viable option.
        
           | IshKebab wrote:
           | I don't think so. Rust solve _a_ class of security issue, but
           | that class is 2 /3 of all security issues! That's
           | overwhelmingly more secure.
           | 
           | And it's not like it has no effect on the final third either.
           | The stronger type system and ownership system make non-memory
           | related bugs less likely too.
           | 
           | Your comment makes it sound as if you have a better solution.
           | But Rust is the best one we know so far, so this kind of
           | naysaying does more harm than good.
        
       | spookie wrote:
       | This makes me think the musl people are right. Glibc is quite a
       | large attack surface.
        
         | loeg wrote:
         | Fwiw, the analogous logic in musl libc[1] uses log_ident set to
         | the empty string if openlog() was called with NULL ident[2]. It
         | also formats twice to buf[1024] without checking that the first
         | format's length isn't in excess of the stack buffer but that's
         | likely always safe given how short the stuff printed in the
         | first format is.
         | 
         | [1]: https://git.musl-
         | libc.org/cgit/musl/tree/src/misc/syslog.c#n...
         | 
         | [2]: https://git.musl-
         | libc.org/cgit/musl/tree/src/misc/syslog.c#n...
        
           | knorker wrote:
           | Interesting. The first snprintf() could return a negative
           | value (at least by the glibc snprintf manpage), and then
           | therefore the second vsnprintf will overwrite... something.
           | 
           | Maybe their implementation can't return negative, or only if
           | the fmt string (constant, here) is invalid? I wouldn't count
           | on it, especially of it being true forever. That's pretty
           | sloppy code.
        
             | loeg wrote:
             | I believe the first snprintf only returns a negative value
             | if the format string is broken. Musl has decided to avoid
             | that by not breaking the format string. If that changes,
             | returning -1 is the least of their problems.
             | 
             | You can see the implementation here: https://git.musl-
             | libc.org/cgit/musl/tree/src/stdio/vsnprintf... and
             | https://git.musl-
             | libc.org/cgit/musl/tree/src/stdio/vfprintf.... .
        
               | bodyfour wrote:
               | Probably the person you're replying to is just confused
               | because before it was standardized some snprintf()
               | implementations returned -1 on overflow. If you were
               | trying to be portable and defensive you'd need to check
               | for either error return.
               | 
               | Not really a concern inside musl because those
               | implementations are probably long gone and because it's
               | calling its own snprintf() anyway.
        
           | o11c wrote:
           | More relevant, it caps `log_ident` to a very short length. If
           | my math is correct, the max length of the first snprintf is
           | 72 bytes (actually less with real-world valid PIDs), and the
           | buffer is 1024.
           | 
           | As usual, MUSL is secure at the cost of giving up usefulness.
           | A 32-byte buffer might seem like a lot, but there are dozens
           | of programs with a name longer than 31 bytes on my system.
           | `dbus-update-activation-environment` is the most interesting
           | (most others are gcc/binutils), and I know dbus in general
           | uses syslog, though I haven't traced this particular entry
           | point.
        
             | loeg wrote:
             | Yeah. You have to math out the various fields but I agree
             | it appears to end up a few orders of magnitude smaller than
             | 1024. It's just not particularly explicit.
             | 
             | > As usual, MUSL is secure at the cost of giving up
             | usefulness.
             | 
             | I would say 'Musl is deliberately simple at the cost of
             | usefulness'. The simplicity lends itself to correctness and
             | security.
             | 
             | Btw, since Musl does not implicitly construct log_ident
             | from argv[0], program name is sort of irrelevant unless
             | that program explicitly openlog()s with argv[0]. dbus-
             | update-activation-environment does not appear to openlog or
             | use syslog at all: https://gitlab.freedesktop.org/dbus/dbus
             | /-/blob/master/tools... Instead it prints error messages to
             | stderr.
        
         | snvzz wrote:
         | In a sane world, most distributions would have long switched to
         | musl and libressl, from glibc and openssl.
        
           | Arnavion wrote:
           | Distributions can't switch to libressl until all the software
           | they package also supports it. libressl is incompatible with
           | openssl, so programs that are compiled against the one cannot
           | run against the other, and both cannot be loaded into the
           | same address space. That's why the few distributions that did
           | switch to libressl eventually switched back.
        
             | o11c wrote:
             | s/address space/link-map namespace/
             | 
             | See `dlmopen(3)`. Admittedly, multiple link-map namespaces
             | are weird to use.
        
       | PhilipRoman wrote:
       | Meh, local privilege escalation.
       | 
       | I think by now everyone has accepted that the Unix/Linux account
       | system is insecure by design and exists just to prevent
       | accidental damage.
       | 
       | There are ways to restrict it but the default configuration
       | simply exposes too much of an attack surface. I still give
       | separate accounts to some services as defense in depth, but it
       | mostly exists to slow down untargeted attacks.
        
         | smarkov wrote:
         | What's the preferred method of limiting permissions then?
         | Virtualization? Containerization? AppArmor?
        
           | christophilus wrote:
           | I use containerization, but it's not perfect. Also on the
           | front page today:
           | https://news.ycombinator.com/item?id=39250975
        
           | PhilipRoman wrote:
           | For normal services namespaces are enough (make sure to set
           | no_new_privs, one of the best Linux features). Run it with
           | the bare minimum of mounts required, no shared /tmp, etc. For
           | all its faults, systemd actually gets this right, by allowing
           | to easily harden services.
           | 
           | Note that this exploit relies on being able to run as root
           | (typically through setuid). If you don't fully trust a
           | service, don't let it ever talk to code running as root in
           | the first place. No opening sockets in /tmp, no listing
           | processes in /proc, no dbus shenanigans, no sudo or su. One
           | of this issues with this was that some programs require
           | setuid for bad reasons (IIRC historically ping was setuid to
           | be able to send ICMP packets). From a quick check (find -type
           | f -perm -4000) most of these problems have been eliminated,
           | via linux capabilities or otherwise.
           | 
           | These tactics successfully saved me from log4shell.
        
           | worksonmine wrote:
           | Depends on what you want to achieve. AppArmor/SELinux prevent
           | access to files and directories. Virtualization and
           | containerization tries to build a jail. You can combine the
           | solutions, a container running a distro with SELinux like any
           | from the Red Hat ecosystem.
           | 
           | They have all had vulnerabilities, My preferred method is to
           | not install stuff I don't need, and fix any dangerous
           | configuration for the programs I do need. I prefer Podman
           | over Docker because of rootless for example.
        
           | marcosdumay wrote:
           | Containerization doesn't protect at all against privilege
           | escalation. And AppArmor is a very partial improvement.
           | 
           | The way to protect against this is with an external
           | supervisor. But then you have to care about privilege
           | escalations attacks against the supervisor. Hopefully that
           | one is much simpler than Linux so it has much fewer
           | vulnerabilities.
        
           | tempmac wrote:
           | extra laptops/servers...? meh
        
       | blibble wrote:
       | at this point I think it's beyond clear any setuid root binaries
       | shouldn't be written in C
        
         | hannob wrote:
         | I mean, I don't disagree, but it probably would not have helped
         | in this case.
         | 
         | This is based on a buffer overflow in glibc itself. It is my
         | understanding that rust still calls into libc for many lowlevel
         | system functions. And if you call C code from rust, well, it's
         | still C code with C code bugs.
         | 
         | Ultimately, if you want to get rid of those, you'd have to
         | rewrite the libc in Rust.
        
           | blibble wrote:
           | I very much doubt a rust program is going to pull in glibc
           | syslog()
           | 
           | in this case it was in a pam module
           | 
           | and dynamic modules loaded into setuid binaries (pam/nss/...)
           | are also super-high risk and should be memory safe too
        
           | grayhatter wrote:
           | > Ultimately, if you want to get rid of those, you'd have to
           | rewrite the libc in Rust.
           | 
           | No, you can eliminate these bugs by getting better at writing
           | code. The language is independent from the quality of any
           | given code.
           | 
           | Rust is capable of introducing theses bugs, but the language
           | is more explicit when you do. One languages isn't more secure
           | than another. They each attempt to fill a niche, some trade
           | language usability for correctness, some trade correctness
           | for being easy to use.
        
             | dcsommer wrote:
             | > you can eliminate these bugs by getting better at writing
             | code.
             | 
             | No, the data doesn't show this. This is the "anger"
             | response to being confronted with the reality of the
             | inherent insecurity of C and C++. https://www.usenix.org/co
             | nference/enigma2021/presentation/ga...
        
         | wongarsu wrote:
         | Given the unix philosophy of providing important functionality
         | only in the c runtime library and having all software
         | dynamically link against it, wouldn't the most crucial step in
         | this be to replace glibc with an implementation that isn't
         | written in C?
        
           | pjmlp wrote:
           | UNIX philosophy has nothing to do with dynamic linking, in
           | fact during the first decade of its existence only static
           | linking was available.
        
         | hossbeast wrote:
         | You are conflating C with glibc
        
       | tremon wrote:
       | > "Although [$reasons], we decided to not pursue this avenue of
       | exploitation, because:"
       | 
       | I see lots of ground for further research.
       | 
       | (from https://www.qualys.com/2024/01/30/cve-2023-6246/syslog.txt,
       | the article from the duplicate HN thread)
        
       | charcircuit wrote:
       | There is 0 reasons to have setuid binaries. Additionally, having
       | users escalate a program to root, with no restrictions on what
       | root can do, is even worse. setuid is a total hack and is
       | embarrassing how many Linux distros are using it.
        
       | deathanatos wrote:
       | ... this C is code is just a case exemplar of the problems with
       | C.
       | 
       | We have `l` and `1` being used in the same passage. Single letter
       | variables are bad to start with, but `l` is particularly cursed
       | since, if you happen to be using a font where `l` and `1` are
       | homoglyphs, good luck reading that passage. But that's sadly _the
       | default_ in a number of OSes /environments, and indeed, _the site
       | this code is hosted on_. One might presume `l` stands for length,
       | ... but there 's also `vl`, `bufsize` and `sizeof bufs`. `bufs`
       | and `buf`, although the former, `bufs`, is a _singular_ buffer,
       | leading one to wonder why the variable name is pluralized. I 'm
       | pretty such it's because it's stack-allocated (as opposed to the
       | heap alloc'd `buf`, and thus `bufs` is "buf[fer], s[tack]". This
       | is just nuts, and it should be no surprise there are multiple
       | CVEs.
       | 
       | The OG code was had CVEs. The commit that "introduced" this --
       | which I almost feel is wrong to say -- attempted to use `bufsize`
       | for the buffer size, which certainly feels more right that the
       | OG, but alas, the variable is never meaningfully initialized.
       | This CVE is on the patch to fix a CVE! And in the original CVE's
       | bug report, the ominous words of "Doesn't seem too serious" were
       | uttered.
       | 
       | I'm sure it's fixed now, though, right?
       | 
       | syslog is just a bad interface logging interface, to boot.
        
       ___________________________________________________________________
       (page generated 2024-02-04 23:01 UTC)