Hacker News new | past | comments | ask | show | jobs | submit login
Testing the memory safe Rust implementation of Sudo/Su (ferrous-systems.com)
91 points by SGran on June 28, 2023 | hide | past | favorite | 139 comments



Currently vendoring all of the dependencies for Rust implementation pulls in 1.75 million SLOC, which I find amusing.

This is a lot of SLOC, and a huge surface area to pull targeted supply-chain attacks, IMO.

P.S.: I know you don't compile in all of this into the binary, yet consider the eyes and work hours required to verify that the whole chain is sane and safe.

This is how it looks:

    vagrant@rust-playground:~/development/sudo-rs$ cloc .
        3549 text files.
        3271 unique files.                                          
         453 files ignored.
    
    1 error:
    Line count, exceeded timeout:  ./vendor/proc-macro2/src/parse.rs
    
    github.com/AlDanial/cloc v 1.86  T=9.67 s (320.2 files/s, 208065.8 lines/s)
    --------------------------------------------------------------------------------
    Language                      files          blank        comment           code
    --------------------------------------------------------------------------------
    Rust                           2637          49276         111413        1754298
    diff                              2            884          32618          35892
    Markdown                        182           4903              9          13341
    TOML                            137            807           1073           5734
    Assembly                          8             90             71           1244
    YAML                              5             80             26            393
    JSON                            106              0              0            120
    reStructuredText                  1             70              4             90
    C/C++ Header                      9              5              2             79
    Bourne Shell                      5             14             17             64
    C                                 2              6              6             46
    Bourne Again Shell                2              7              8             41
    Python                            1             17             19             38
    Dockerfile                        1              0              0              2
    --------------------------------------------------------------------------------
    SUM:                           3098          56159         145266        1811382
    --------------------------------------------------------------------------------


Quite a few of those ~1 million lines seem to be in libc-type wrappers, which is roughly analogous to libc for C (which also isn't included).

If I remove those the line count is about 100k, which is about the same as sudo.


> Quite a few of those ~1 million lines seem to be in libc-type wrappers, which is roughly analogous to libc for C (which also isn't included).

I didn't know that Rust programs run without a libc.


They don't. The way it's set up is:

The stdlib only exposes some syscalls via rust-friendly wrappers, and in a way that is generally a cross-platform subset, sometimes with platform extentions. So the `fs` module (https://doc.rust-lang.org/std/fs/) exposes common file operations across most (all?) supported platforms. Some unix specific file operations are exposed at: https://doc.rust-lang.org/stable/std/os/unix/fs/index.html . These things do a pretty good job for most work, but sometimes you gotta get wierd...

The libc crate allows more direct access to the syscalls on your flavor of *nix by creating a rust-> bridge for them, and exposing the C types directly. This bridge isn't a lot of code, mostly it just does the work of creating a rust function that minimally wraps the external C function. For a lot of low-level software you end up pulling the libc crate to hit your system specific calls.

And for some very system specific calls (e.g. io_uring) you end up having to pull in another crate that calls into that subsystem for you (often pulling in libc also).

All of this ends up being linked to the libc you build against tho.


Speaking from nearly total ignorance of Rust, I don't understand why things can't be hidden in a ton of wrappers and interfaces.


What’s being counted here is the interfaces and wrappers.


Rust programs can run without a libc:

1. on Linux

2. in embedded

The vast majority of them do use libc, as the underlying platform requires. The default on Linux is to use a libc as well.


Doesn’t matter. It’s still quite a big haystack to hide some tactical needles here and there.


It does matter, because to get a comparable number from sudo you'd have to count libc. I don't know how many lines of code that is exactly (and e.g. musl or OpenBSD libc is probably smaller than GNU, so there isn't a fixed number to start with), but I bet it's roughly a comparable number, or at least much closer.

As a general point I agree with you, but both sudo and sudo-rs are pretty large.


libc is used by the entire system. It's not a vendored dependency of just sudo. At best, you'd amortize it over everything that uses it to get a reasonable comparison.


Are we not on the trail to doing the same with rust, at least in theory? You don't think someone somewhere has the idea to make an all-rust system eventually? Even without that evetual ultimate expression, if you have even a few rust components in your system and not just sudo alone, then the same point stands. You don't count rust itself any more than you count gcc.


> You don't think someone somewhere has the idea to make an all-rust system eventually?

In progress. Looks good so far. I haven't tried it, though.

https://www.redox-os.org/


The sudo-rs Cargo.toml [1] file seems very reasonable. This is the curse of being cross platform. The inclusion of https://github.com/Stebalien/tempfile as a dependency is responsible for the overwhelming majority of lines due to including *-sys crates for multiple OSs.

    ~/Code/tempfile !! tokei vendor
    
    ===============================================================================
     Language            Files        Lines         Code     Comments       Blanks
    ===============================================================================
     GNU Style Assembly      8         1405         1276           39           90
     C                       1            3            2            0            1
     Shell                   1           23           18            1            4
     TOML                   25         1536         1178          234          124
    -------------------------------------------------------------------------------
     Markdown               29         2193            0         1560          633
     |- C                    1            2            2            0            0
     |- Rust                11          277          220           19           38
     |- TOML                 8           60           58            0            2
     (Total)                           2532          280         1579          673
    -------------------------------------------------------------------------------
     Rust                  917       811953       792140         4536        15277
     |- Markdown           304        17252          124        14364         2764
     (Total)                         829205       792264        18900        18041
    ===============================================================================
     Total                 981       817113       794614         6370        16129
    ===============================================================================

[1]: https://github.com/memorysafety/sudo-rs/blob/60985b2f5f7ffa8...


and tempfile is not even used as a runtime dependency. It is only ever called, or even linked, in tests.

(by the way, Herman, much belated thanks for putting together the original Rust LA meetup all those years ago!)


Curious: Does cargo not distinguish between dev and runtime dependencies?


It does, but all dependencies are put in the same folder, which is why the original poster conflated them.

If you look at the linked Cargo.toml you'll see

  [dependencies]
  libc = "0.2.139"
  <snip>

  [dev-dependencies]
  pretty_assertions = "1.3.0"
  tempfile = "3.5.0"


In general, you have a very valid point, but how many lines of code do we need to build the normal sudo if you are generously adding stuff we don't compile into the binary? Compiler, tools, some machine with some userspace and kernel for those to run on and so on?


Doesn't matter. Both implementations run on the same platform and poisoning the compiler for both versions are equally probable.

If you can pull a lower level attack with general purpose toolchain, targeted for either implementation, it's a more impressive feat, for sure.

However, Rust implementation adds a significant SLOC on top of that complexity.


Arguing that complexity comes from SLOC feels like paying per LOC... it sort of misses the point.

The languages are different - a lot of C behavior feels "inferred" or "implicit". A lot of Rust behavior is explicit, that is you have to write down exactly what's happening. So things like casting a void* to a $whatever require a couple of lines of rust, not just a single line (or fragment) of `($whatever *) p`.

My personal experience is that the explicit nature of Rust is pretty nice when visiting new code, or revisting code I wrote a while back - everything is written down for me, whereas I have to puzzle out a lot of behavior from the C. It's a bit annoying at first, "cmon compliler, why do I have to tell you this?" is still a common refrain in my head, however its worth it in the long run - revisits to the code are much faster to grok/reload, and once I got used to it, writing it down as it was all loaded in my head the first time wasn't so much of a pain anymore.


I’m on the sudo-rs team: we are actually very mindful of the dependencies we use. Our current main branch already uses significantly fewer dependencies than a few months ago. Aside from security it also really helps with adoption since it makes packaging way easier. A large part of the output you got is due to a tempfile dependency, which is a dev only dependency that is not touched when compiling a release/dev binary, only during testing a small part of this would be used. I say a small part because most of it is related to tempfile running on windows, which is irrelevant for sudo-rs since we don’t support or intend to support windows.

Just for good measure though and to prevent any further discussion about this I’ve removed the tempfile dependency from the main sudo crate as our usage of it could easily be replaced by a simple timestamp/pid combination. But again, this really only affected testing, I think the size of the code that ends up in the final binary is very reasonable.

As for people suggesting supply chain attacks via dev dependencies: I doubt we would be the final target of such an attack: i.e. what an attacker would really want is access on all/some machines that have sudo-rs installed. The only way to do that would be to change the release artifacts, which dev dependencies do not have the ability to change, at least not directly, so such an attack would only be the first step in a chain of attacks. I have a feeling that there are way easier and less detectable ways of manipulating us than by using modified dev dependencies. Of course that doesn’t mean we should ignore the risks.


You're probably running into this: https://github.com/rust-lang/cargo/issues/7058

`cargo vendor` will download dependencies and dev dependencies for all platforms, which leads to a lot of unused code being pulled in. In this case, the Windows API and Microsoft compiler wrappers.

In this instance, during the build process "tempfile" is used as a dev-dependency, which has a runtime dependency on windows-sys when compiling Windows binaries. I'm not entirely sure why (commenting it out in Cargo.toml doesn't seem to break the build).

After commenting it out and manually removing the spurious Windows API files as well as the unrelated packages (`cd vendor; rm -rf ctor diff output_vt100 pretty_assertions proc-macro2 quote syn unicode-ident yansi win*`), I get the following results:

          0.0358 secs
    ┌───────────────────────────────────────────────────────────────────────────────────────┐
    | Language                        files        size       blank     comment        code |
    ├───────────────────────────────────────────────────────────────────────────────────────┤
    | Bash                                3    939.00 B           7           2          30 |
    | C                                   1     1.31 KB           5           6          44 |
    | C Header                            1    226.00 B           0           0           7 |
    | D                                  15    31.75 KB          32           0         143 |
    | JSON                               22    39.69 KB           0           0          22 |
    | Markdown                           16    53.46 KB         425           0        1054 |
    | Rust                              396     4.98 MB       13852        9502      131650 |
    | Shell                               5     2.24 KB          11          18          50 |
    | Toml                               14     9.60 KB          54          61         319 |
    | Yaml                                2    10.14 KB          70           0         341 |
    ├───────────────────────────────────────────────────────────────────────────────────────┤
    | Sum                               475     5.13 MB       14456        9589      133660 |
    └───────────────────────────────────────────────────────────────────────────────────────┘

As a comparison, this is the output for https://github.com/sudo-project/sudo:

       0.0439 secs
 ┌───────────────────────────────────────────────────────────────────────────────────────┐
 | Language                        files        size       blank     comment        code |
 ├───────────────────────────────────────────────────────────────────────────────────────┤
 | Autoconf                          124     1.90 MB        2618        4317       59031 |
 | C                                 365     4.20 MB       15977       22626      111340 |
 | C Header                           90     1.14 MB        1816        4911       18803 |
 | JSON                                7     9.22 KB           0           0         236 |
 | Markdown                           10   133.62 KB         676           0        2498 |
 | Pascal                              3    33.63 KB          79           0         925 |
 | Perl                                2    12.81 KB          54          83         306 |
 | Plain Text                          1     15.00 B           0           0           1 |
 | Protocol Buffer                     2     5.54 KB          22           0         185 |
 | Python                             10    26.41 KB         152         259         295 |
 | Shell                              77   358.96 KB        1589        2534        8961 |
 | Yaml                                4     7.98 KB          16          38         205 |
 ├───────────────────────────────────────────────────────────────────────────────────────┤
 | Sum                               695     7.81 MB       22999       34768      202786 |
 └───────────────────────────────────────────────────────────────────────────────────────┘
It should be noted that the sudo project's dependencies and autogenerated code aren't included in this overview


Thanks for this summary. I was going to write something about the safe rust sudo depending on a load of handwritten assembly but it seems that's a spurious windows thing.


The dependency list[0] looks pretty reasonable, AFAICT the overwhelming majority of that line-of-code count comes from autogenerated Windows API methods.

edit actual counts:

  $ cargo vendor && cd vendor && {
      for p in * ; do
      echo -n $p
      tokei $p | rg '^\s+Rust'
  done } | sort -n -k 4 | tabulate
  ----------------------------  ----  ---  ------  ------  ----  ----
  errno-dragonfly               Rust    2       9       8     0     1
  windows_aarch64_gnullvm       Rust    2      11       9     0     2
  windows_aarch64_msvc          Rust    2      11       9     0     2
  windows_i686_gnu              Rust    2      11       9     0     2
  windows_i686_msvc             Rust    2      11       9     0     2
  windows_x86_64_gnu            Rust    2      11       9     0     2
  windows_x86_64_gnullvm        Rust    2      11       9     0     2
  windows_x86_64_msvc           Rust    2      11       9     0     2
  winapi-i686-pc-windows-gnu    Rust    2      25      13    12     0
  winapi-x86_64-pc-windows-gnu  Rust    2      25      13    12     0
  windows-targets               Rust    1      54      46     3     5
  output_vt100                  Rust    2      67      55     0    12
  cfg-if                        Rust    2     164     131    16    17
  instant                       Rust    4     316     260     6    50
  countsctor                    Rust    2     331     254    21    56
  errno                         Rust    5     375     280    41    54
  diff                          Rust    4     561     485     9    67
  autocfg                       Rust    9     702     558    41   103
  yansi                         Rust    7     741     627     3   111
  signal-hook-registry          Rust    3     818     566   150   102
  fastrand                      Rust    4     830     710    16   104
  hermit-abi                    Rust    4     847     601     5   241
  pretty_assertions             Rust    5    1231    1072    33   126
  glob                          Rust    2    1589    1291   113   185
  bitflags                      Rust   20    1715    1373   105   237
  unicode-ident                 Rust   11    1794    1697    36    61
  signal-hook                   Rust   17    1969    1520   147   302
  tempfile                      Rust   15    2367    1928   102   337
  quote                         Rust   17    2458    1979   148   331
  redox_syscall                 Rust   23    3595    2996    83   516
  log                           Rust    9    3635    2970    97   568
  io-lifetimes                  Rust   15    4218    3605    80   533
  proc-macro2                   Rust   17    5286    4514   139   633
  cc                            Rust   13    5861    4767   488   606
  rustix                        Rust  236   39927   33837  1467  4623
  syn                           Rust   92   51956   48946   493  2517
  libc                          Rust  224  109836   99688  2073  8075
  linux-raw-sys                 Rust   61  145628  145455    84    89
  winapi                        Rust  405  179933  176630  3299     4
  windows-sys                   Rust  281  497624  497608     4    12
  ----------------------------  ----  ---  ------  ------  ----  ----
[0]: https://github.com/memorysafety/sudo-rs/blob/60985b2f5f7ffa8...


And this is what that small list of dependencies pulls in:

https://github.com/memorysafety/sudo-rs/blob/60985b2f5f7ffa8...


And most of those dependencies are only transitive via [dev-dependencies], which causes the entire Windows API to be pulled in.


Still doesn’t invalidate the fact that it’s a lot of surface area to hide a targeted attack.


If you're intending to install the package on an image, [dev-dependencies] are not going to be included in the package. So, no, it's not actually relevant to the surface area of the package.


It matter.

The great grandparent was talking about audit for supply chain attacks.

dev-dependencies can run on dev machine in compile time


If a C library used Python to run its tests, I don't think we would consider the whole Python interpreter to be part of the software supply chain for that library. Sure it's possible that running tests on a build machine could let an attacker corrupt the build later, with a bad PyPI package or something. But that feels more like a "not having a clean build environment" problem than a "this project has too many dependencies" problem. I think the fact that Cargo manages these two lists in the same file makes the relationship feel tighter, but I'm not sure it's actually tighter.


I see your point, but if you're going to consider all code that runs on the dev machine as a source of supply chain attacks, that's going to include all LOC for the Linux kernel. And the LOC for dev's web browser that they use to browse the issue tracker. And so on.


It's weird. You'd think Rust has been around long enough that they could finally take the step toward a mature implementation with a stable ABI and support for shared libraries.

It's OK as long as you're just prototyping the language and need to make changes fast. But I'd hope that we could one day actually build a real system in Rust.


As others have mentioned, there is a long list of sudo CVEs that are unrelated to memory safety. I didn't see it mentioned in the article, but I hope that they have mined the CVEs for tests to ensure they don't accidentally reintroduce a known vulnerability. I was also surprised to see no mention of tests originally written for ogsudo, surely there are some?

Overall, I appreciate the rewrite-it-in-rust 'movement', I think it is an excellent learning opportunity for people who may not otherwise bother with learning the details of the foundations of our modern systems. And, as in this case, taking a detailed look at the original can improve the original.


Do you know if sudo uses negative tests for their CVE fixes?

If they are written in such a way that they are portable (i.e. execute sudo, send mangled data, inspect response) it shouldn't be too hard to run it against the new version.

At least that is what I try to practice in fixing all kinds of bugs. Write test that proves the bug, fix the bug, write test that proves bugfix works, invert the test that proves the bug.


I'm not sure, I only see static analysis and fuzzing workflows in the CI on GitHub [1].

[1] https://github.com/millert/sudo/actions


One of the famous non memory safety bugs in sudo recently was related to using a sigil, which Rust would have prevented because the natural and easy solution in Rust for those use cases is an Enum/Sum Type.

Which is to say that Rust has safety features beyond just what we are used to from garbage collected languages. Sum types, stricter typing, data race protections, etc.


A whole laundry list of languages have stricter typing than C, garbage collected or not.


And yet, Rust is being taken seriously in many places where those languages have been unable to displace C. I wonder if it hadn't taken 15 years for a FOSS Ada compiler to become available, maybe it would have taken off.


Ada never had the same level of marketing power towards engineers that Mozilla came up with. GNAT still came out well before Rust did.


I suppose my point was that Ada had missed it's shot-by the time GNAT came out, the buzz of an exciting new language expired before people could easily use it.

I'm not familiar with the marketing that Mozilla did, but whatever they did it does seem to have been effective. However, I do think that the Ada mandate by the DoD would be equivalent or more effective in kick-starting an ecosystem.


DoD software and the commercial / FOSS world rarely intersect. The last time the government tried to set a computing language standard, we got COBOL, so it's probably for the better such a thing isn't tried again.

The marketing Mozilla employees have came up with is creating a Categorical Imperative for using Rust. The person in the comments section of a C project or any CVE bug will be saying, this would never have happened if this was written in Rust, why aren't you writing this in Rust? The moral thing to do was to write it in Rust! And a lot of software engineers are eager to think and talk like this, because it gives them something more exciting in their lives beyond writing bean counters for selling widgets. Pure functional languages already had a bit of that attitude, but I don't think it caught on nearly so much because actually writing pure functional code is very difficult even for most engineers.


Is there evidence that this was a concerted marketing effort? That behavior seems pretty consistent with the normal culture of the sector, vim vs emacs comes to mind.

I have found rust difficult to learn, and I continue to develop and maintain embedded systems with C. I say this not to distance myself from rust, but to indicate my neutrality on the subject.


I call it marketing, but I agree that it developed naturally. But I think it goes much further in excess than the typical emacs vs vim brinksmanship.


> The last time the government tried to set a computing language standard, we got COBOL,

Ada, actually (well, that might not be the last time, but it is certainly much more recent than COBOL.) COBOL and JOVIAL were roughly concurrent – both efforts started in 1959, I believe – but the effort that culminated in Ada started in the mid-1970s.


I should have clarified that I meant a universal standard. COBOL was meant to create a common language across business and government, while Ada was meant to just consolidate a language across DoD projects. Ada is a much better language than COBOL, but I think that adoption of even a good language across those ecosystems will only succeed when it is adopted from the ground up.


COBOL also wasn’t a government-launched initiative, it was an industry initiative that went to the government for funding, unlike Ada which was born as a DoD initiative.

If COBOL was worse for its time than Ada was for its time (which I don’t think is clear, though I’d rather work with Ada than COBOL if I had to pick one today), maybe that would support an argument about customer-need driven projects vs. vendor-imagination-driven projects.


We did indeed look at and are still looking at the list of CVEs where sudo takes part right now. However, many of the bugs are through one way or another not applicable for our implementation (yet). For example CVE-2014-0106 is only applicable when env_reset is disabled, but we purposely do not support that mode. Or take for example CVE-2023-28487 which concerns sudoreplay, but right now we have no plans to add that functionality.

Sudo does have a suite of regression tests, but they mostly test specific implementation details which makes it hard to port to our codebase. Our test suite tests against the integrated artifact, which allows the easy switching between the two implementations (and also allowed us to find a few bugs in the original sudo).


If you want to move away from Sudo, but don't want to try this rust implementation just yet, I have had great success with OpenBSD's doas. It has been ported to every Linux distro I know of as well:

https://github.com/Duncaen/OpenDoas


There's also a straight port of doas:

https://github.com/slicer69/doas/

However unlike sudo and opendoas this does not implement the persist feature on not-OpenBSD.


> If you want to move away from Sudo

Is there something wrong with sudo?


doas is great with 2 caveats (from my experience on debian): the persist feature has a sketchy implementation that didn't work; and plenty of other software assumes you use sudo and may require it as a dependency.


Regarding software support - the situation is definitely improving. I remember being pleasantly surprised that various Arch package management tools support it out of the box and I didn't even bother installing sudo on Debian.


Is sudo known to be memory _un_safe? Because otherwise, calling this one "the memory safe [Rust] implementation of Sudo" is a bit weird.


On a basic level: programs written in C almost always have memory access patterns that can't be proven safe generally. sudo, in particular, has had a few public bugs over the past few years that directly trigger potentially exploitable memory unsafety[1][2]. Note that not all bugs receive public reports, much less are assigned CVEs.

Rust's memory semantics are safe by construction: unless you intentionally write the the part of the language that requires you to explicitly mark things as unsafe, your programs cannot contain the kinds of temporal or spatial memory bugs that can occur in C and C++. Given that, calling this the "memory safe" implementation seems pretty reasonable, in the same way that calling a Java or Python implementation of sudo "memory safe" would also be reasonable.

[1]: https://www.cvedetails.com/cve/CVE-2021-3156/

[2]: https://www.cvedetails.com/cve/CVE-2019-18634/


Sudo 1.8.0 to 1.9.12 (the latter is from 2023(!)) are memory unsafe.

Sudo before 1.9.5p2 is memory unsafe.

Sudo before 1.8.26 is memory unsafe.

Sudo before 1.6.6 is memory unsafe.

Source: https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=sudo


most of the issues linked are not memory related.

Am I missing something?


That each of the versions dannymi listed had at least one potentially exploitable issue that a natively memory safe language would have mitigated, versions actively used in production as recently as this year even by people who keep bang up-to-date?

I'm sceptical that the chance of introducing new and interesting bugs of other varieties with a rewrite is worth the protection offered by the new environment¹, but either your dismissal of dannymi's point is rather disingenuous or you genuinely were missing something that had been stated quite obviously…

--

[1] except where existing problems are so systemic that fixing them in the original stack would effectively be a partial rewrite anyway, so susceptible to the same risk


> That each of the versions dannymi listed had at least one potentially exploitable

Can you please point me to each one of those?

I can only see a few of them.

> memory safe language would have mitigated

potentially, assuming there are no bugs anywhere in the toolchain.

> but either your dismissal of dannymi's point

I wasn't dismissing no one's point.

I simply scrolled through the list of issues and found out that the majority of them were things like (picked them randomly)

systemd before 247 does not adequately block local privilege escalation for some Sudo configurations, e.g., plausible sudoers files in which the "systemctl status" command may be executed. Specifically, systemd does not set LESSSECURE to 1, and thus other programs may be launched from the less program. This presents a substantial security risk when running systemctl from Sudo, because less executes as root when the terminal size is too small to show the complete systemctl output.

An issue was discovered in Zimbra Collaboration (ZCS) 8.8.x and 9.x (e.g., 8.8.15). The Sudo configuration permits the zimbra user to execute the NGINX binary as root with arbitrary parameters. As part of its intended functionality, NGINX can load a user-defined configuration file, which includes plugins in the form of .so files, which also execute as root.

A privilege escalation vulnerability in FortiNAC version below 8.8.2 may allow an admin user to escalate the privileges to root by abusing the sudo privileges.

It was found that cifs-utils' mount.cifs was invoking a shell when requesting the Samba password, which could be used to inject arbitrary commands. An attacker able to invoke mount.cifs with special permission, such as via sudo rules, could use this flaw to escalate their privileges

I am genuinely asking if memory safe languages could prevent this kind of issues, which represent the overwhelming majority of the issues reported on that specific page, and how.


> Can you please point me to each one of those?

By the magic of find-in-page:

1.9.5p2, CVE-2021-3156

1.8.26, CVE-2019-18634

1.6.6, CVE-2002-0184

1.8.0-to-1.9.12, CVE-2022-43995

I've not read into them in detail, but they are all overflow issues that other languages could have mitigated. Of course they may mitigate them by the task falling over at run-time, but that is “failing safer” than continuing with (potentially deliberately) corrupted data.

---------------------------------

I only spotted the extra details you added when I submitted my first reply (had the reply form open for a while due to work distractions):

> I am genuinely asking if memory safe languages could prevent this kind of issues, which represent the overwhelming majority of the issues reported on that specific page

That page is listing all issues in sudo logged in that database, from which the relevant data was summarised (basically the OP was citing his source). No claim was being made that increased built-in memory safety would prevent all the issues (or even many of them) in that list.


> 1.9.5p2, CVE-2021-3156

> 1.8.26, CVE-2019-18634

> 1.8.0-to-1.9.12, CVE-2022-43995

interestingly these would not be a problem in a language like Pascal that is not memory safe, but has runtime bounds checks enabled by default or C++ that has flags to enable it

So we are left with one over at least a 100

another interesting thing IMO is that the bugs have been sitting there for a long time, so they are probably not obvious and nobody can be sure there aren't similar sleeping bugs in today's software written in safer languages.

This is not against memory-safe languages, mind you, I prefer Rust over C or C++ any day, I simply found that the original claim seemed too bold compared to the actual data and failed IMO to prove that sudo is ridden by easily exploitable memory bugs.


> That each of the versions dannymi listed had at least one potentially exploitable issue that a natively memory safe language would have mitigated,

I'm not seeing that in the list. Of the listed CVEs, not many of them are due to memory safety.


> I'm not seeing that in the list.

All the versions/ranges listed are associated with relevant CVEs on the list, which of the original posters claims can you not see backed up by that list?

> Of the listed CVEs, not many of them are due to memory safety.

That is a list of all the CVEs for sudo, no claim was made of implied that they were mostly due to issues that memory safety would help with, the poster gave the link as a source for information that was presented.


> no claim was made of implied that they were mostly due to issues that memory safety would help with

he literally did

this is the entire message

Sudo 1.8.0 to 1.9.12 (the latter is from 2023(!)) are *memory unsafe*.

Sudo before 1.9.5p2 is *memory unsafe*.

Sudo before 1.8.26 is *memory unsafe*.

Sudo before 1.6.6 is *memory unsafe.*

memory unsafe here is used for dramatic purpose by the author

it actually means that

Sudo 1.8.0 through 1.9.12, with the crypt() password backend, contains a plugins/sudoers/auth/passwd.c *array-out-of-bounds error* that can result in a heap-based buffer over-read. This can be triggered by arbitrary local users with access to Sudo *by entering a password of seven characters or fewer. The impact could vary depending on the system libraries, compiler, and processor architecture.*

in this light, is PHP memory safe because it enforces runtime bounds checks on arrays?

Or

In Sudo before 1.8.26, if pwfeedback is enabled in /etc/sudoers, users can trigger a stack-based buffer overflow in the privileged sudo process. (pwfeedback is a default setting in Linux Mint and elementary OS; however, **it is NOT the default for upstream and many other packages**, and would exist only if enabled by an administrator.)

has one of these bugs ever been exploited?


> he literally did

He literarily, both in the traditional (literally) and modern (figuratively) meanings, did not.

Stating that the page is the source for the information summarised does not mean the same as claiming all/most of the information in the page specifically refers to those cases.


You’re missing the ones that are


Only six entries mention overflows, only half of those involve the heap.


A number of those entries reference multiple overflows.

And a memory safe language will protect against some overflows on the stack (overflows within frames corrupting other values, rather than errant loops that blow the stack by creating too many frames and/or too large frames).


Rust will protect against all overflows on the stack (up to compiler bugs, tier 2 and lower platforms do not necessarily get this guarantee).

If you use too much stack space, it terminates the program. It does now however allow for arbitrary code execution like most C compilers do.


Are you aware of what an exploitable scenario of using too much stack space looks like?

The buffer needs to be so large that it not only exceeds the offset to the guard page, but it reaches a non-faulting address. Lastly it needs to be accessed from the front first rather than the back.

I don't know if compilers commonly generate benign memory accesses from the back of the buffer for large stack allocations to get the page fault handler going. I thought that they did after some prominent Linux exploits in this area. If they do do that, this is safe. Also, this issue would also affect the rust compiler, so they must employ that strategy if this works.


Yeah, if you really wanted to be extra super secure, you can always rewrite it in Java or something.


Sudo has displayed an endless parade of heap overflows and suchlike. It is written in the extreme YOLO style by people with very poor taste.


> Sudo has displayed an endless parade of heap overflows and suchlike.

sudo is 43 years old though

> It is written in the extreme YOLO style by people with very poor taste.

Sounds a bit exaggerated to me. Do you happen to have data on this?


How many hundreds of thousands of lines of code do you think sudo is?

How many hundreds of thousands of lines of churn do you think happen per year in sudo?

If your answer is: What do you mean hundreds of thousands? That is the right question, but the wrong answer. The answers being around ~500,000 and on average ~200,000, including this year, respectively.

In contrast, OpenBSD doas, which exists to serve the same primary purpose of executing commands as a super user, clocks in somewhere around a few hundred to maybe 1 or 2 thousand lines total just eyeballing it.


I must have a fundamental misunderstanding of exactly what sudo does. That is so much code.


sudo integrates with PAM, parse command, sends email, record session logs, do IPC.....


Turns out even in the 80s the so-called “UNIX philosophy” wasn't so ubiquitous…


> If your answer is: What do you mean hundreds of thousands? That is the right question, but the wrong answer. The answers being around ~500,000 and on average ~200,000, including this year, respectively.

> In contrast, OpenBSD doas, which exists to serve the same primary purpose of executing commands as a super user, clocks in somewhere around a few hundred to maybe 1 or 2 thousand lines total just eyeballing it.

It seems to me, with those numbers, that the big problem with sudo is not the language it is written in but the extremely large attack surface.

I would guess that for 1 out of every million invocations of sudo, all that extra functionality is needed. For the rest of the time, its merely being used to execute a single command as root.

We could make systems more secure by simply removing sudo from those systems that don't use that extra functionality, and replace it with doas.


> In contrast, OpenBSD doas

That's my point, sudo survived 43 years, had to support a myriad of different platforms and configurations, doas was started in 2015.

incidentally OpenDoas is written in C too.

We'll see in 35 years how doas is doing.


> Do you happen to have data on this?

Writing security-critical software entirely in C is prima facie evidence of a disqualifying lack of good taste.


Most software that handles actual human lives on the line is not written in plain C. Writing 'security critical' software in C is a trade off - many people are willing to accept extra defects in return for the benefits of the C ABI and performance.


To be blunt, I don't think that this really matters very much. The reason is that if you used Pascal or Modula-2 , both of which use "counted strings" you would very likely have the same kind of safety.

I think the potential of Rust is greatly oversold, because it only ever gets compared to known-unsafe cases, such as string handling in C; there are plenty of other languages that handle this safely out of the box.

And Modula-2 is in the GCC 13 compiler and FreePascal has been out for over a decade at this point; both languages are far more compact and have formal specifications...


> I think the potential of Rust is greatly oversold, because it only ever gets compared to known-unsafe cases, such as string handling in C; there are plenty of other languages that handle this safely out of the box.

The comparison is apt because of scope and scale: Rust is intended to integrate into and eventually replace C codebases; both Pascal and Modula-2 can integrate with C, but AFAIK do not place substantial emphasis on doing so (nor have substantial amounts of community interest in doing so).

Similarly: people keep doing string handling in C, so that is what is going to continue to matter. They also keep doing other things in C that Rust prevents, which are also going to continue to matter. The fact that they're known to be unsafe doesn't change that.

Put another way: it's unclear that the interest in Rust (which is immense) would ever translate into equivalent interest in Modula-2, any technical merits aside. The reality is that Rust does these things well, and a large number of people like it and are successfully using it to replace or augment C codebases. Both conditions are necessary.


80% of bugs come back to memory safety… don’t think it’s overselling it.

Sure other language can do better here too, but you’re not just getting memory safety from Rust.


> 80% of bugs come back to memory safety… don’t think it’s overselling it.

I think you are misremembering the statistic.

It's 70% of security bugs that are related to memory safety: https://www.zdnet.com/article/chrome-70-of-all-security-bugs...

The way you say it, it means that out of every 100 bugs, 80 are due to memory safety.

The reality is that out of every 100 security bugs, 70 are due to memory safety.

For example, a codebase with a thousand bugs might only have 10 that are security bugs, of which 7 are due to memory safety.

You imply that a codebase with a thousand bugs have 800 memory safety issues.


The framing in the last two lines only makes sense if you consider the amount of total bugs as a prior. In practice, the more realistic prior is something that's more observable, e.g. the number or rate of published CVEs or of security breaches. If something like that can be reduced by 70%, that's much more significant than your framing of "0.7% of all bugs in this example are security bugs due to memory safety" makes it out to be.


I don't disagree with that, I was disagreeing with OP whose claim implies that out of every 1000 bugs, 800 of them are due to memory issues.

OP was wildly off the mark.


This would be an argument in favor of using a GC'ed language, because that would reduce those bugs by 99.9%.


Bugs mean the software's behaviour is outside what was specified, but for security we're allowed to depend on any aspect of the specified behaviour. So, all bugs are security bugs.

Some bugs aren't as important as others, but they're all just bugs.


> So, all bugs are security bugs.

That's an unreasonably wide and rather useless definition of "security bug".

> Some bugs aren't as important as others, but they're all just bugs.

I think nobody questioned that bugs are bugs :)


> So, all bugs are security bugs.

For security software, maybe.

For everything else? I think you have not worked on much software touched by the end-user.

A few years ago, on a security-critical product (chipcard/swipecard terminals)[1] written in C++ I specifically did an analysis on tickets logged as a bug over a 3 year period.

It's a fairly mature product with the original devs who built it still working on it.

Very large surface area - 100s of thousands of deployments at merchants, sales ringing up at all those merchants almost constantly, with a variety of different inputs that the terminal had to handle[2]. The environment can be so random in its input at times it's halfway to fuzzing-in-the-field.

IIRC (I am no longer there), there were maybe a few hundred tickets out of a dozen thousand or so that were actual bugs.

Of those bugs exactly three were memory safety issues.

So, your statement:

> So, all bugs are security bugs.

appears to me to be entirely imaginary and fantastical. To me, who has been developing professionally since the min-90s, that statement appears to be purely fiction. I've not worked on any system where even close to 10% of the bugs turned out to be security issues.

"Missing postcode field in address input" is not a security issue. "Retry not attempted when host is down" is not a security issue. "Incorrectly rejects valid ID number" is not a security issue.

The clear majority of bugs are not security issues, even in a product which is intended to be a security product.

[1] I was an EMV developer.

[2] In case you think this is a simple thing, think Foreign Currency purchase, foreign issuer, Fallback from Tap->Chip->Swipe due to corrupt cards or floor/merchant/bank/cardholder/issuer limits, cancellations of transactions that may/may not have completed due to network errors, power cycles in the middle of a transaction, card switch-out attempts, PoS errors, EMI causing random data corruption in serial transmission lines (those chillers next to the till that hold Red Bull and things have compressors come on and go off intermittently, causing spikes).

Then there's also the fact that the terminal sold cellphone airtime for various SPs, PAYG electric vouchers, scanned QR codes, fingerprints. They also, when deployed to forecourts, had code to handle fuel prices and quantities automatically, split out receipts for oil separate from petrol and accept rewards cards linked to retailers.

When deployed at a restaurant, it had code to allow atomic multi-party payments, even when each party was on a different switch, to a different bank, to a different issuer.

It had code to talk to the most popular PoS systems (each one using a different application layer protocol even if they were all using BER/DER at the base layer).

That's not counting the health monitoring code, or the OTA updates code, or the TLS code (yes, it had its own because certifying OpenSSL or similar was just as expensive as rolling your own with the bare minimum necessary, and certifying that), or code specific to each bank, or key injection code, or cryptography code.

It's anything but small or simple. The average YC startup almost certainly has nowhere near the same amount of complexity, nor does the code you are used to working with have anywhere near the same amount of criticality or attempts to exploit.

In other words, this is a fucking complicated source codebase, with constant attackers who aren't interested in DDoSing you, and severe financial repercussions per second when it is down, while profiting successful exploits immensely.


> I was an EMV developer.

I almost couldn't ask for a better example. EMV is so broken that arguably even if systems did behave as specified that's a security bug anyway, because it doesn't fulfil people's basic expectations of how such a system should work.

For people tuning in at home, EMV stands for Europay, Mastercard, Visa, it's the standard for modern "chip and PIN" type payment cards, including "tap to pay" designs and these days is controlled by a consortium or something which allows all the various payment card outfits to dick each other over equally rather than just those three.

Anyway, you seem to just assert that there weren't security bugs, but with no evidence whatsoever for your claim. I have no doubt that you confidently believed that things which actually were serious security bugs in your EMV terminal were fine. After all, why would this standard be ludicrously fragile and have key security requirements that you might destroy by mistake ? Only something designed by idiots would do that...

I think the main thrust of your view is that you deemed the tickets in your ticket system (which may have been labelled "bugs") not to really be bugs at all. That's a very different semantic argument, and mainly I'd say you should take it up with management. At my current employer we distinguish requests, incidents, cards, tickets, bugs and work items, and it feels like that's maybe too much, but whatever.


In security software like sudo you could argue that nearly all bugs are security bugs. But there are plenty of bugs in non-primarily-security software where it's really hard to argue that there is any security implication.

In any case the reason for the figure is because when there is a buffer overflow that allows overwriting the stack its almost always a security bug because it usually allows untrusted input to make the program do anything. So of all the kinds of bugs a program can have the memory safety ones are usually security bugs, while other logic bugs may or may not have meaningful security implications.

In a suid program that is designed to launch other programs (e.g. sudo), significant logic bugs are very likely to have security implications too.

This is supported by sudo's CVE list-- the vast majority appear to be logic bugs with no memory safety involvement. One argument made for rust is that with memory safety "in the bag" developer time can be better invested in other areas. But it isn't safe to assume that: Rust is a very different language and other properties of it such as its far greater complexity than C may make logic bugs more likely. As far as I know the defect rate in rust software has never been seriously studied.

It would be a huge relief if there were some serious analysis that could provide some confidence that rust wouldn't make the software quality situation worse. Especially since I'm not convinced that trading memory safety bugs for other bugs alone would be an improvement: At least memory safety flaws are always bugs, so when valgrind, memcheck, stack protector, etc tell us about them during testing or formal methods tell us they exist (which hardly can exist for rust given that the language isn't even formally specified) we always know we have an issue. With general logic bugs it's much harder to be confident that a behavior is a bug.

And, of course, memory safety issues can arise in rust, due to compiler/library bugs or because other restrictions or performance issues have caused users to use unsafe or depend on external library code... so it's unclear how much we can really say that memory safety is in-the-bag. I think to be confident that a critical piece of rust code lacks memory safety bugs we still must test comprehensively, including with fuzzing and we must review its dependencies which are almost always far far far far worse than in C (creating a whole issue for sneaking in bad code).

Informally, my experience with software written in Rust is that it is far more likely to crash (panic) on the spot when I attempt to use it than software written in C-- usually because the author just hasn't bothered with error handling due of some mixture of the language making it somewhat harder to do, out of a culture that doesn't regard code that panics unexpectedly as shoddy work (because unlike a crash in C, it's unlikely to be the sign of a potential vulnerability), or in some cases because the goal was just "rewrite in rust" and the programmer didn't take much care or pride in their work or lacked the subject matter expertise to do it competently.

It's heartening to see this effort has a test suite-- Of course (AFAIK) sudo doesn't have one itself but arguably sudo's test suite is the decades of use it's had in production. A newly written program has a lot more need for a test suite than an established one.

I think rust programs are more likely to have some amount of tests compared to really old free software, but usually not enough to compensate for their newness.


> It's heartening to see this effort has a test suite

A test suite which sudo (here named "ogsudo" although I'm not sure how "gangsta" sudo really is) failed, and as a result sudo's authors had to fix sudo as described in the article we're discussing right ?

> Informally, my experience with software written in Rust is that it is far more likely to crash (panic) on the spot when I attempt to use it than software written in C

Sure, in the C when the author wrote no error handling, it just presses on. Is its internal state now incoherent? Who cares, error handling is for pussies, YOLO. In the Rust it panics. I'd argue that for many cases, including sudo, the panic is much better though still not the ideal outcome of course.

The canonical "Hello, world" program in C and in Rust when given a stdout that claims to be full behaves differently. In C it just exits successfully. Sure, it couldn't actually output the "Hello, world" message, but who checks errors anyway? In Rust it panics, nobody told it how to handle the condition where the output device is full.


> A test suite which sudo (here named "ogsudo" although I'm not sure how "gangsta" sudo really is) failed, and as a result sudo's authors had to fix sudo as described in the article we're discussing right ?

Yup which is table stakes, as the original software didn't have tests (but did have decades of use in production, which is why it has less need for tests than the rewrite).

> Sure, in the C when the author wrote no error handling, it just presses on. Is its internal state now incoherent? Who cares, error handling is for pussies, YOLO. I'd argue that for many cases, including sudo, the panic is much better though still not the ideal outcome of course.

My concern there is that because bad things happen the C program has the error handling, while in rust it may be "don't handle that case? who cares? rust is safe(tm)".

It's not necessarily the case that both are yolo. I'm sure you've heard of risk compensation. https://en.wikipedia.org/wiki/Risk_compensation

The kind of experience I've had with rust code isn't a case where the c-analog is yolo. You just don't expect widely used C code to crash on some command-line argument misuse or when a file is missing, it's not entirely unheard of but it's not common. In my experience it's extremely common in rustlandia. In that sense, rust culturally has really managed to give me a very strong yolo vibe.

"It can just panic" is no so good when the code is in a library that called from the motor controller for a forklift holding a pallet over your head. ... and in plenty of other less drastic situations.

I agree for sudo most of the time panicing is probably not directly unsafe (though it might produce an outage that causes harm). For software that deals with more complex external state, panicing can still be pretty bad-- like leaving the system in a vulnerable state, leaving confidential information laying around, etc. There are plenty of examples where a simple DOS attack can be used to compromise a system, e.g. DOS a master system to cause a fall over to a slave.


> no so good when the code is in a library that called from the motor controller for a forklift holding a pallet over your head

There should be a mechanical fail safe. The Therac-25's first important difference from prior hardware was that they removed the physical fail safes because hey, nobody will write a program that's incorrect. Yes they will, and it killed people - so stop designing hardware which assumes unreasonable things about software. With an appropriate fail safe if the code panics, the forklift locks up, maybe there's some sort of mechanical reset needed - this is annoying but nobody is dead.

When the firmware in your elevator freaks out, which isn't even a rare occurrence, nobody dies because the hardware people don't trust that firmware. If you let the firmware control it directly, you'd be lucky if anybody used them because they'd be too dangerous. I've watched Third Year undergraduates in electronics learning with (toy) elevators how to write real time software and they were not good at this. The toys were designed so that they weren't damaged when (not if, when) the students smashed the elevator into the top or bottom of the shaft and still kept the motors running.

> My concern there is that because bad things happen the C program has the error handling,

The example we just looked at is common, and in fact the C program doesn't have error handling, it's just ignoring the error because caring about errors is more trouble in C so why bother.

> The kind of experience I've had with rust code isn't a case where the c-analog is yolo

You're going to have to give a lot more details. It's so easy to YOLO in C that it's harder to think of cases you can't just YOLO than cases where you can. C loves functions where if your inputs are invalid you get invalid outputs, which you can then blindly supply to another function where likewise the invalid inputs result in invalid outputs. YOLO all the way.


On mechanical fail safes, they're sometimes impossible, wildly expensive, or simply less safe. In some devices the safe region is a complex polytope defined over a dozen degrees of freedom and may not even be static.

Perhaps for cases where someone will literally be squished when it fails you can draw a bright line rule, but you can turn the safety requirements down some-- instead of being crushed they might just lose a finger, or a million dollar float glass batch will be lost. At some point the risk isn't worth the elaborate hardware failsafe and software will be all you've got. There really isn't a safe threshold in any case: maybe it's just a word processor but its failure costs someone days of work and leads to their suicide. Software reliability matters.

Panicing on exceptional situations is often only slightly more acceptable than crashing or corrupting in other ways (and sometimes less, commonly the corruption is inconsequential).

> caring about errors is more trouble in C

That hasn't matched my experience. YMMV I guess. It's my experience that it's the exception rather than the rule that rust programs won't just panic instead of handling errors sensibly, in particularly because wrapping types to express failure and explicitly handling it along with all the required lifetime management adds a lot of complexity.


> here named "ogsudo" although I'm not sure how "gangsta" sudo really is

It's a joke.


Having developers mindshare is a quality of its own.


Or Ada for that matter! Memory safe languages are not something new, nor are memory safe languages that lack a slow runtime.


So 6 out of 174 CVEs could have been avoided this way. ... but how many more of the unavoided logic errors will be created by using a language which is far more complicated, less clear, and readable/reviewable to far fewer people?

That said, It's a great sign for that the tests that it was was comprehensive enough to find bugs in the original sudo. But a set of tests isn't really complete until it finds a compiler bug too. :)


I think getting a fresh perspective from fresh developers is still a net positive, some of the CVEs are truly embarrassing for such a critical piece of code.

That being said I skimmed the list of CVE and yeah Rust wouldn't have prevented anything. The rust evangelism is exhausting.


getting fresh perspective, or repeating the same mistake.

Many logic error were discovered in real life use cases over many years


It's finally my turn to link this often linked essay: https://www.joelonsoftware.com/2000/04/06/things-you-should-...

I don't believe I've ever seen it shared in the context of rewrite-in-rust, but I think the views it expresses also have some value in this context.


On the other hand, part of the goal is to strip out accumulated feature creep. Sudo apparently has a lot of functionality which I never knew existed, all of which could lead to future bugs.

There are quite a few Unix tools which I think could use a similar refresh if not frozen in time thanks to POSIX.


No doubt 80% of the users only use 20% of the features... but do they use the same 20%? :)

I think sudo is often the wrong tool for what people are using it for today-- it's a tool to delegate on a multi-user system. But today people are most often applying it on a single(-ish) user system to raise or lower their permissions.

Ironically, most of the vulnerabilities that sudo has had aren't really of consequence in that modern single user usage: If the attacker can run code as the user, then they'll be able to take over any privilege raising process the user uses even if the 'sudo' tool were flawless.

Once you leave that use case the greater feature set of sudo (including, perhaps some of those crusty features you mention) has more applicability.

The thing to always keep in mind is that the programs cruft is substantially the body of its embedded knowledge. Some of that knowledge might be mistaken or outdated. If you really knew what parts were what-- you could just remove them.

If the rewrite isn't sure it can remove it and just replicates, it may not have understood the function's purpose enough to replicate it faithfully and could even introduce security problems (or cause users to introduce them by forcing them to bodge around configuration statements that no longer work).


The expressive type system and the borrow checker /also/ allow you to avoid many of the logic bugs that you’d have otherwise.


In the case of memory safety, we can say that absent compiler bugs, use of unsafe (including by dependencies), or callouts to other languages that rust defiantly eliminates essentially all memory safety bugs.

We can't make the same strong statement about the type system preventing other bugs. The type system in rust requires a LOT more boiler plate code an extra typing, some of which can introduce bugs.

I am not aware of any study that would support the claim that rust reduces the defect rate overall and I'm somewhat doubtful of claims that it does because of the absurd regularity that rust software immediately panics on me when I attempt to use it (and, the amount of firfox crashes I've experienced which of late are almost entirely rust code panicking under the hood). It's also possible that what I'm seeing are all defects that would have existed otherwise made slightly more visible, if so -- it's a good thing.

But we shouldn't let the fact that we can take for granted that rust avoids memory safety bugs by construction from meaning that its other properties which MAY reduce defects over all actually do. Hopefully it does. But writing software entails a lot of cost and risk to undertake for a hope and plenty of things that have been intended to make things better actually made them worse.


I know it's hard to make a strong argument about this, and also to gather data on it. But there is https://opensource.googleblog.com/2023/06/rust-fact-vs-ficti... , section "Rumor 5: Rust code is high quality – Confirmed!", which touches upon this.

That said, personal experience has shown me that modeling data using types (instead of abusing, say, string-to-string dicts as is common in Python, or passing "untyped" strings around for things that /are/ strings but that have higher meaning, like file system paths) is very beneficial to program correctness. Obviously this is doable in many languages, but Rust helps enforce this due to the lack of implicit promotions and general adoption of these idioms in standard/common libraries.

As one specific example, I originally wrote sandboxfs (a FUSE file system) in Go and then rewrote it in Rust -- part because I hit performance issues, and part because I wanted to learn Rust. Trying to reproduce the original structure of the code in Rust was not doable, and having to adapt it to Rust highlighted various logic and concurrency bugs that existed in the Go version but that silently went unnoticed.


That's a good testimonial. Also thanks for the link.

I'm a big fan of strongly typed software, but at the same time I've absolutely seen bugs introduced where people were forced to be explicit in conversions and got it wrong, when the automatic thing would have been the right thing. Maybe the answer to my concern is just that there is no replacement for care and competence and that bad software can be written regardless of the tools.


> but how many more of the unavoided logic errors will be created by using a language which is far more complicated, less clear, and readable/reviewable to far fewer people?

Can we stop pushing this false narrative about Rust being sooooo overly complicated? The real reason this narrative is pushed is because lazy developers don't want to take the time to learn a language that forces them to think. Yes I said it. Most of these developers want to go on autopilot just copying/pasting code from others and not even think about security in the slightest.

Rust is actually really shallow in what you have to learn to write safe code.

For me, languages like JavaScript, python, and C++ are the real complicated languages. Which is why I haven't "learned" (is that even possible) them.


Rust is a dramatically more complex language than C (what sudo is written in!).

If you force the comparison with C++ then your position has more merit, however, I think there is still an argument that the subset of C++ that you must deal with in a given codebase can be (and often is) simpler than the portion of rust you must deal with in any rust codebase. I'm not particularly confident of this position, however, and it's tangential to the point that rust is unambiguously more complex than the language used by sudo.

Aside, there is an enormous amount of thoughtless copy and paste in rust, just as there is in other language. Rust also comes with a culture of extremely promiscuous dependency use, it's not uncommon to build a rust program and watch the compilation download and build two different SSL libraries! -- even a program that you have no interest in using with HTTPS/SSL at all.

Maybe in spite of the bad dependency culture rust actually will mean an advance in software quality in practice. But I think we simply don't have evidence of that (yet), and it's overly hopeful to assume that this will be the outcome simply because it was the intent of the creators of rust.

The fact that any criticism or concern gets mobbed an that there is so much mindless advocacy from people that haven't even considered issues like the trusted-computing-base problem doesn't speak well for the prospects that rusts' own problems that limit its benefit will be addressed.


Which is why I didn't mention C along with those other languages that are spaghetti. C is simple, clean, and won't get in your way. That last part is why most serious bugs occur. Unless the language is built from the ground up to prevent the bugs from compiling then you will always have vulnerabilities.

I know that C is a beautiful language and Rust is ugly but at some point we have to realize that humans are error prone and will always produce buggy C code.

As for Rust dependency issues, here's a very simple article for beginners:

https://marketsplash.com/tutorials/rust/rust-dependencies/

There's also the cargo tree command to find duplicate dependencies:

cargo tree --duplicates

By far more better than the dependency hell that is Node.js :D


Duplicates aren't the main cause of dependency hell in Node.js, although they certainly don't help matters.

Other systems languages have kept simple, C-like syntax without the associated error rates. Removing a lot of implicit casting and having sane compiler standards is all you need, not a bad implementation of the HM type system.


sudo doesn't have 174 CVEs. People always link to a simple mitre keyword search, and many just mention sudo in passing; you don't have to look far: the two first items on that list are not about sudo at all.

The last time I looked at this[1] I found the most serious problems were logic issues, and not memory issues (although there are some of those a well).

[1]: https://news.ycombinator.com/item?id=35756093


> using a language which is far more complicated, less clear, and readable/reviewable to far fewer people?

By that reasoning we should rewrite all these tools in JavaScript or Ruby, Python or some such. Clearer (than C), more readable and readable/reviewable to far more people.

Edit: to be clear: I think that would be an extremely dumb idea.


> By that reasoning we should rewrite all these tools in JavaScript or Ruby, Python or some such. Clearer (than C), more readable and readable/reviewable to far more people.

> Edit: to be clear: I think that would be an extremely dumb idea.

I dunno if it really is that dumb.

Why can't sudo be rewritten in Go? The performance difference is not likely to be noticed and there's no runtime platform dependence (as with Python, et al).

And, with managed memory, you'll have even fewer memory-safety bugs than with Rust.

What exactly makes Rust a better fit for sudo than Go, bearing in mind that Go is a whole lot safer than Rust.


How is Go a lot safer than rust?


Any GC language is a lot safer than Rust.

Seriously, anytime you have to manage memory yourself it's going to include the danger that you mismanage it. Rust does not prevent all types of memory mismanagement.

What's more, the burden is on the programmer to manage memory; all the Rust compiler tells you is where there are potential (not actual) memory errors. The programmer still has to fix them, and even with that there are still a number of memory errors that will get through.

Compare to a GC - allocate an object and then forget about it. No problems at all.


All of JS, Ruby, and Python are vastly more complicated languages than C. They do have a shallower learning curve. It's unclear to me what degree they have a bigger review audience: there are an awful lot of write-only JS programmers! :)


Rust is far more readable and reviewable than C code. As attested by 1000 Googlers.


I wouldn't trust Googlers as a measure of any sort of quality, given their love of Go.

C code relying on tons of macros will be harder to read then C code that delegates preprocessing to another language. Rust code that leans heavily into the ML features and unwrap.parent.unwrap.parent.unwap... will be a lot more unpleasant than Rust code written to just get the bit shifting job done. I imagine it really depends on the code base.


You might be overestimating the popularity of Go within Google.

That said, a tool like sudo should be written almost entirely in a very high-level language that is easy to use and review. C, C++, or even Rust should be reserved for places where the system interface requires it, if there are any such points of interface.


On the flip side, languages like C,C++ and Rust have the major benefit of having next to no runtime component to it, allowing trace-driven fuzz testing to achieve a much higher level of confidence in its test coverage. For a tool like sudo, this can matter a lot.


I think this is really important and it's a big part of what makes it unrealistic to write critical software in Python or Java: It's too slow to get extremely deep testing, even with fancy tricks like snapshotting the execution state to avoid startup costs.

But, that said, Rust code compiled in debug mode which required to get integer overflow detection is slow enough that it severely degrades the ability to use fuzz testing on many codebases, FWIW. I believe the reason is that debug mode always disables numerous optimizations that are required to make rust performant at all because of all the boilerplate emitted by earlier stages of compilation.

AFAIK there isn't a way to get the equivalent of GCC's "-fsanitize=undefined" (or -ftrapv) for checking for unexpected overflows at a performance cost similar to "-fsanitize=undefined" performance cost on C code.

It's still a much better situation than python or java, I think-- but an area that could use improvement which won't be improved if rust is above criticism.


You can pretty trivially turn on overflow checks yet compile with all of the other release options as normal. It's one line in cargo.toml or one flag to rustc.


What is the one line? (I shall benchmark!)


iirc it's `-C overflow-checks=y` to rustc or

  [profile.release]
  overflow-checks = false
in cargo.toml.

I would probably use the flags with RUSTFLAGS to make sure that it goes to all dependencies and not just the crate you're building, if what you're building has dependencies.


Thanks!


… I just realized that second should have been true not false. Lol. Hope I didn’t lead you astray.


A job for Common Lisp!


Huh? Go has its tradeoffs, some people don't like it and that's fine. But nobody can deny that it is by far one of the most 'readable and reviewable' (the argument in question) mainstream languages.


The praise of Go didn't end with it being just readable, but also 'brutally pragmatic' and all that.

If Googlers think that Rust code is just as readable as Go, even though Rust obviously makes tradeoffs against readability for other features, I would be tempted to mark that as Googlers just having a culture about being overeager to think the tools they are using are the best.


Why can't I deny that it is readable and reviewable? I don't think it is, basically at all. It gets evangelized as if it is but I have yet to see a compelling argument that can convince me. I've had the displeasure of having to dive into some codebases and I specifically hate it for that reason.


Well, I can't speak for you, of course. I also haven't seen that code you reviewed. And I won't try to evangelize you. I'd say, in a nutshell, that Go is readable and reviewable because its grammar consists of the C-style basis that most every imperative language has, and not much more. It's the lack of features that makes it very 'WYSIWYG'.


I think java is way more readable (and safe) than C - we should all switch to. Proof by authority w/o any background is not useful


Solaris had a bit of Java in it, and it was a terrible fit on account of how slow the JVM startup was and how large its memory footprint. Now that was almost 20 years ago and by now there's ways to optimize the JVM for short-lived processes, but it's still a pain and it's still a huge installation just for a small program like `sudo`. No, something like `sudo` needs to be natively compiled, or if bytecompiled then to a tiny VM -- tiny in every way: tiny opcode set, tiny bytecode interpreter, tiny install size.


You're not wrong, yet I'd sooner kill myself.


I think your complaints about Rust would depend on the quality of the code. You can go wild, or you can keep things simple.


Memory safety is just the tip of the iceberg of issues with sudo. Repeat after me - sudo is not a security tool. Maybe if you stick with a tiny subset of it then yes. But that subset would be better off being its own program (doas).


Why would it be better being it’s own program?




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: