[HN Gopher] Sudo-rs dependencies: when less is better
       ___________________________________________________________________
        
       Sudo-rs dependencies: when less is better
        
       Author : marbu
       Score  : 73 points
       Date   : 2024-03-26 22:06 UTC (1 days ago)
        
 (HTM) web link (tweedegolf.nl)
 (TXT) w3m dump (tweedegolf.nl)
        
       | awoimbee wrote:
       | > In the end, we chose the potential dangers of reimplementing
       | command line parsing over the potential issues of including clap
       | 
       | Have you considered using argh ? Seems like it has the upsides
       | without the downsides.
        
         | telotortium wrote:
         | Don't think it's worth it. Looking at sudo's man page at
         | https://linux.die.net/man/8/sudo, it looks like sudo only uses
         | single-letter flags, some of which take arguments. Argh
         | implements long options, built-in parsing, subcommands, and
         | lots of other nice to have features that nevertheless add a lot
         | of code. It's normal in traditional UNIX C programs to parse
         | sudo-style flags in a handful of lines without any external
         | dependencies.
        
           | 0cf8612b2e1e wrote:
           | I consider single letter flags only to be a mistake. There
           | should almost always be a verbose double-dash option.
           | 
           | I get it, most of the tooling which uses single letters is
           | totally ossified due to backwards compatibility reasons.
           | However, the sudors team is already breaking backwards
           | compat. Now is the time to make a minor usability
           | improvement.
        
           | scbrg wrote:
           | That's a bit dated. Both regular sudo (1.9.13p3) and sudo-rs
           | (0.2.2) on my machine (Debian) support double dash style
           | options.
        
         | IshKebab wrote:
         | I've used argh a fair bit. It has some weird ideas and
         | restrictions and generally isn't nearly as good as clap. I
         | would definitely recommend clap (unless you have extreme
         | security concerns like this).
        
         | Karellen wrote:
         | Why not use `getopt()` which already exists in libc?
         | 
         | (Or even `getopt_long()` if you're Linux/glibc-only? Author
         | mentions not supporting Windows, but is unclear whether non-
         | Linux Unices, e.g. *BSD, are intended target platforms.)
         | 
         | https://manpages.debian.org/bookworm/manpages-dev/getopt.3.e...
        
           | steveklabnik wrote:
           | If you're trying to implement as much in Rust as possible,
           | keeping an important part of the codebase in C code feels
           | like the wrong decision, in my opinion.
        
       | photonbucket wrote:
       | Is there any tooling which can tell you exactly which parts of a
       | crate that you actually use and produce a minimized version for
       | vendoring/auditing?
        
         | 0cf8612b2e1e wrote:
         | I like this idea. Theoretically, the compiler already has the
         | machinery to remove dead code. Next step could package up just
         | the source you touch.
        
         | Arnavion wrote:
         | You can get that info from code coverage, via `cargo llvm-cov`
         | etc, though that would require exercising all code paths into
         | the deps or else you might underestimate how much of the deps
         | you need to vendor. But at least if you underestimate in this
         | way, you'll probably just get a compiler error rather than
         | anything breaking at runtime.
        
         | dathinab wrote:
         | it's not trivial to do if you have multiple build targets and
         | features
         | 
         | i.e. you would need to vendor one version for each features x
         | target tripple combination combined with cfg expansion and
         | (proc) macro expansion inlining and then a static reachability
         | analysis to prune all unused code (and dependencies). That
         | would likely not be good enough so you probably need to have
         | some runtime code coverage analysis to find "likely dead code"
         | (but not statically provable dead code) and then manual choices
         | to keep/remove combined with some bisecting/testing to make
         | sure the choices are sane.
         | 
         | Afik such tool doesn't exist.
         | 
         | And it's non trivial.
         | 
         | But it's also very viable to create it.
        
         | jcgrillo wrote:
         | I have been spitballing about this recently too [1]. The way
         | I'd imagine it would work is the toolchain takes one pass over
         | your crate, compiles everything, then takes another pass to
         | trim all the dead code from your vendored deps. Then your git
         | diff basically has your code + all the lines of all your deps
         | that didn't get trimmed.
         | 
         | There would probably need to be some more work to make it more
         | user friendly, but I think it's really important that _all_ the
         | code which ultimately ends up in your binary goes in the diff
         | otherwise reviewers won 't actually look at it.
         | 
         | Disclaimer: I don't know enough about compilers, or the Rust
         | toolchain specifically, to know if this is even possible or
         | whether it would actually help anyone in the real world. But it
         | seems "naively reasonable" for some definition.
         | 
         | [1] https://news.ycombinator.com/item?id=39828499
        
       | sebazzz wrote:
       | If they don't link libc statically it can become a problem if the
       | system-installed libc is corrupt or incompatible. My Arch install
       | broke once and I wasn't able to run pacman to correct it, because
       | the libc installed was not compatible with pacman. If sudo
       | wouldn't run, I would not even have a chance to repair the
       | install without booting to live cd.
        
         | Arnavion wrote:
         | What distros are there that normally dynamically link
         | everything but statically link sudo? OpenSUSE, Debian and
         | Ubuntu (the distros I have on hand) do not, at least.
        
           | paholg wrote:
           | I just checked on NixOs, and ldd reports sudo is not dynamic.
        
             | justinsaccount wrote:
             | Did you check the real sudo binary, or the setuid wrapper?
             | 
             | On my system sudo is `/run/wrappers/bin/sudo` but that is a
             | setuid wrapper for `/nix/store/z008bzqrl2zc848gjhh04012jhxp
             | l72q-sudo-1.9.15p5/bin/sudo` which is dynamically linked.
        
         | dralley wrote:
         | If the system-provided libc is corrupt, isn't sudo the least of
         | your concern? What else is going to _work_?
        
           | gkbrk wrote:
           | > What else is going to work?
           | 
           | Everything that was statically compiled.
        
           | wizzwizz4 wrote:
           | A statically-linked busybox, which is often enough.
        
         | Quekid5 wrote:
         | I'd invest $10 into a rescue USB stick regardless. I like
         | putting Ventoy on it and having a large number of different
         | distribution ISOs on there, just in case.
        
         | jokethrowaway wrote:
         | Funnily enough something similar happened to me after doing a
         | partial update. Doing a partial update are not supported in
         | arch linux for this very reason.
         | 
         | sudo broke as well as many others command. ssh worked for a bit
         | and then segfaulted. I edited my PATH to have a healthy version
         | of libc but things kept breaking in different ways (version
         | mismatches) In the end I had to use a live usb drive as I
         | couldn't write to /usr/lib
        
       | thevidel wrote:
       | > including crates for platforms such as Windows, which we
       | obviously would not require as a Unix utility.
       | 
       | Probably a little less obvious now that Windows has their sudo?
       | 
       | https://learn.microsoft.com/fr-fr/windows/sudo/
        
         | pvg wrote:
         | This also had a bigass HN discussion recently, for those
         | interested https://news.ycombinator.com/item?id=39305452
        
       | MuffinFlavored wrote:
       | > We replaced it with our own argument parsing once we noticed
       | that adopting clap was taking more code than doing it ourselves.
       | 
       | I feel like it's obvious that there are two sides to this echoed
       | throughout the "programming" community:
       | 
       | 1. Don't pull a package in for what you can do yourself because
       | it might have 500 dependenices for no good reason
       | 
       | 2. Don't roll your own, use something off-the-shelf third-party
       | that is actively maintained, open-source, well written/easily
       | usable/fleshed out, etc.
       | 
       | They conflict...
        
         | cryptos wrote:
         | Yeah, but that is what makes engineering interesting. You
         | always have to find the right balance with your trade-offs.
        
         | steveklabnik wrote:
         | It is true that you cannot simply repeat maxims others have
         | declared and expect that the job gets done well. Our profession
         | (like many, many others, if not all!) requires judgement to do
         | the best job. Different situations may call for different
         | decisions.
        
           | MuffinFlavored wrote:
           | > Our profession (like many, many others, if not all!)
           | requires judgement to do the best job.
           | 
           | And is almost permanently open to retrospect + disagreement
           | of "you shouldn't have done that this way and followed maxim
           | A, you should've followed maxim B instead" and vice versa...
           | :)
        
             | steveklabnik wrote:
             | Yeah, time is one of those factors that can change, and tip
             | the scales one way or the other.
        
         | jbverschoor wrote:
         | They don't conflict:
         | 
         | 1+2 -> Pull a package that is well maintained and doesn't use a
         | ton of packages.
         | 
         | The problem is the language platform / "culture", for example
         | js
        
           | MuffinFlavored wrote:
           | But in this post we're seeing this "mindset" trickle to the
           | Rust ecosystem, for something as "complicated" as command-
           | line argument parsing
        
             | ekidd wrote:
             | Clap is a really fantastic command-line argument parser,
             | especially using the "derive macro" they now include. Once
             | you start dealing with git-like subcommands, and other
             | complex cases, it Just Works. You get help, short and long
             | options, defaults, repeated arguments, deserialization to
             | custom types, etc. Essentially everything is accessible
             | with a couple of lines of code.
             | 
             | Life's too short to build all of this each time. I'm
             | perfectly happy to ship 2-5 MB zip archives, which is where
             | a lot of my more complicated Rust tools wind up.
        
               | epage wrote:
               | (maintainer of clap)
               | 
               | In this situation, if they were truly concerned about
               | clap, I think they should have gone down to lexopt
               | (https://docs.rs/lexopt/) rather than roll their own
        
             | mort96 wrote:
             | I believe it actually has a lot more to do with the tools
             | than some "mindset" in the community. You don't see this
             | sort of thing in C++ really, because deep trees of
             | transitive dependencies are painful. While Rust... has
             | pretty much the exact same package management style as
             | Node, so it doesn't surprise me that it has similar
             | results.
        
               | cmrdporcupine wrote:
               | Worth pointing out that Java also has automatic
               | transitive-dep package mgmt courtesy Maven & friends, or
               | at least has had since the mid 00xs. And while it does
               | have some amount of dependency explosion it's not this
               | bad.
               | 
               | Why? Because a) Maven central is moderated better b)
               | Maven has <exclusions> to override crap if necessary. c)
               | The JRE includes a much richer standard library that
               | doesn't force you to rely on 3rd party deps for things
               | like random number generation or HTTP calls.
        
             | sunshowers wrote:
             | Command-line parsing with good errors and help really is
             | quite complicated. Clap doesn't have that many unnecessary
             | features.
        
           | cmrdporcupine wrote:
           | 100% this, and I really hope we can turn the Rust culture
           | ship around on this front.
           | 
           | I've been ranting about this a lot, and getting about a 50%
           | upvote vs downvote ratio :-)
        
           | sunshowers wrote:
           | This sets up some bad incentives.
           | 
           | If someone decides part of a package is useful and extracts
           | it out into another crate, then that will count as a demerit
           | going by this rule, even though it should be rewarded.
        
         | arp242 wrote:
         | I don't think they necessarily conflict, and good programmers
         | will pick what's appropriate for the context. The problems
         | start when either path 1 or 2 gets followed dogmatically.
         | 
         | I do have to say I'm closer to 2 than 1. Most code really isn't
         | that hard to write, and just solving just your own problems
         | instead of the general case can simplify things _a lot_. And
         | the code is 100% under your control, which can also have its
         | advantages.
         | 
         | Some programmers seem afraid to write code. The amount of
         | contortions I've seen folks pull just to re-use some package
         | that wasn't really a good fit (or just wasn't a good package to
         | start with) has at times been bewildering. In the most extreme
         | case I replaced a badly working solution someone spent half a
         | year on with something that worked well in just a week, just
         | writing it from scratch (add another week or two for bugfixes,
         | so that's 3 weeks).
         | 
         | On the other hand I've also seen people NIH the silliest of
         | things. In the most extreme case here they had done their own
         | templating, i18n, database layer - the works. That would have
         | been okay if it worked well, but all of it was ad-hoc junk.
         | 
         | For example they did their own flag parsing for $reasons, and
         | only "--flag=value" worked and not "--flag value". I spent
         | quite some time being confused by this because it also didn't
         | error on the wrong usage (it just did the wrong thing...) They
         | gave me shit for nOt REadINg tHe dOCumENtAtiOn. Like, mate,
         | I've never seen any tool where "--a=b" works but not "--a b"
         | before or since, and I just used the space-variant out of habit
         | without thinking. They didn't fix the flag parser, and I wasn't
         | allowed to either. Didn't work long with these spanners.
         | 
         | Nothing wrong with doing your own flag parsing necessarily; I
         | did my own flag parsing for Go because I don't like the stdlib
         | package and others I could find. Waste of time? Maybe. But it's
         | my time to waste and at least it works well.
         | 
         | The problem with NIH usually isn't the NIH part, but shitty
         | programmers writing shitty code part.
        
       | epage wrote:
       | For some more detail on the choices that went into this, see
       | https://www.reddit.com/r/rust/comments/1b92j0k/sudors_depend...
       | 
       | For myself, I think people focus too much on "dependency count"
       | and not what those dependencies represent. For example
       | 
       | - If a subset of a package is pulled out, it is no longer a "zero
       | dependency" package and some people look down on it.
       | 
       | - Whether you use a dependency or write your own, the logic has
       | to exist. The main question is if there is a difference in
       | priorities.
       | 
       | Applying those
       | 
       | - I really wonder about their claim that using clap took more
       | code than doing it themselves. I also wonder about "not using
       | many features" as there are a lot of usability features in clap
       | that aren't items you check off on a list. If dropping clap, it
       | should have been replaced with https://docs.rs/lexopt/ rather
       | than rolling their own
       | 
       | - While rpassword had its problems, it would have been better to
       | work upstream or create your own competition to upstream, rather
       | than locking away the improvements within sudo-rs
       | 
       | - I think its the right choice to keep glob. So long as it
       | implements the spec of interest, bringing it in doesn't buy you
       | much while keeping it external gives you the whole "many eyes"
       | situation
       | 
       | - I agree about dropping `thiserror`. It can be nice for
       | prototyping or high churn code but if you write-and-forget your
       | errors, you are carrying around that weight for nothing.
       | 
       | - Its unclear why they merged all of the sudo-* packages into
       | sudo-rs. I wonder if those would have been cases where they
       | benefit everyone for being split out for reuse.
        
         | nindalf wrote:
         | Agreed with everything you've pointed out. There seems to be an
         | implicit assumption that all dependencies are bad, even though
         | it'd actually be better to refactor their own code to a crate
         | under their maintenance. Almost as if they think the people
         | evaluating the security of this will apply a simple heuristic
         | like "if number of deps is more than x, this software is
         | insecure".
        
         | dathinab wrote:
         | Agree the dependency count is mostly meaninglessly.
         | 
         | What matters is how many vaguely defined "entities"
         | (people/groups/companies) you trust and how trustable each of
         | them is.
         | 
         | Also there are not really zero dependency libraries, you always
         | have some dependencies, e.g. the compiler implicitly is a
         | dependency too. And so is your build system, and your languages
         | standard library, and libc, etc. etc. So obsessing with "0" is
         | like obsessing with "1.0" releases or abusing type systems,
         | i.e. not helpful at all.
         | 
         | Additionally you can have "crate" dependencies, but you pin (or
         | even vendor) them and give them a though "supply chain risk"
         | review and them keep them pinned or require a another review.
         | Sure you still have to keep track of stuff like bug fixed
         | yanked versions etc. But for a lot of smaller crates it's
         | feasible. In difference to some other languages it's quite easy
         | to do so in rust (for many crates, for larger ones which have a
         | lot of functionality where you might need bug fixes, maybe even
         | for security this isn't that viable, but then in most projects
         | there is only a very small number of such dependencies if any
         | (e.g. tokio, rustls).
        
           | nindalf wrote:
           | > how trustable each of them is
           | 
           | I think this is the important point. They've removed clap
           | (argument parsing library) as a dependency, but they continue
           | to trust cargo (the rust build tool) that uses that library
           | and is primarily maintained by the same developer?
           | 
           | I feel like if they're willing to trust the developers of the
           | standard library and the official compiler and build tool,
           | then they might as well trust clap as well.
           | 
           | This feels like removing dependencies just to say they did.
           | But it may turn out well. Maybe there are "dependency
           | skeptics" who will be won over when they see fewer
           | dependencies.
        
             | steveklabnik wrote:
             | Clap ends up in your binary, Cargo does not.
        
               | abigail95 wrote:
               | if cargo was malicious it would affect the binary, which
               | is the point
        
               | nindalf wrote:
               | Trusting trust Steve.
        
         | steveklabnik wrote:
         | I don't have any special insight to this decision, but
         | 
         | > - Its unclear why they merged all of the sudo-* packages into
         | sudo-rs. I wonder if those would have been cases where they
         | benefit everyone for being split out for reuse.
         | 
         | You play to your audience. If someone decides not to use sudo-
         | rs because it's in multiple packages, that may be a bad reason,
         | but they're still not choosing it, and that's a worse world
         | than if they did.
         | 
         | I would probably do the same thing, even though I am very much
         | on the other side of this debate from the zero-dependency
         | folks. The intended audience is probably much more full of
         | folks who do believe that.
        
       | ecliptik wrote:
       | How does this compare to OpenBSD doas[1][2]?
       | 
       | 1. https://man.openbsd.org/doas
       | 
       | 2. https://cvsweb.openbsd.org/src/usr.bin/doas/
        
         | steveklabnik wrote:
         | > Our current target is to build a drop-in replacement for all
         | common use cases of sudo.
         | 
         | In my understanding, the same general comparison as doas to
         | good old regular Classic (tm) sudo. They're going for
         | "basically the same thing, but with some stuff removed" rather
         | than a re-think of the tool.
         | 
         | It's like harm reduction: the idea is to be able to replace
         | sudo with a memory-safe version where sudo is already
         | entrenched in a workflow, not to be a successor that's somehow
         | better in a more abstract sense.
        
       | dathinab wrote:
       | There is also cargo vendor (which turns dependencies into path
       | dependencies).
       | 
       | Sometimes if you do security sensitive stuff it can be a good
       | option to either:
       | 
       | 1. pin dependencies and give each dependency a review for
       | suspicious code
       | 
       | 2. vendor them in some cases (e.g. applying patches, or if
       | pinning seems to not be good enough for whatever reason likely
       | related to offline building)
       | 
       | If you are not a very security sensitive project but still worry
       | about the supply chain then it may also be an option to
       | pin/vendor some dependencies but e.g. trust `tokio`, `regex` or
       | similar.
       | 
       | E.g. not pin some more trusted dependencies but then pin some
       | small utility crate from a random person which you don't want to
       | write yourself and is trivial/self contained enough so that you
       | likely might not care about any updates to it (still include it
       | into security scans check why it was updated etc.).
        
       ___________________________________________________________________
       (page generated 2024-03-27 23:01 UTC)