7 ms·
The Linux kernel will fix some peculiar argv usage in execve(2)
- hardware2win 3y ago>First, you can pass in a zero-length argv (ie, where argv[0] is NULL), in which case the executed program will have an argv[0] that is NULL. A variety of programs will then be unhappy with you, as people discovered in CVE-2021-4034. This option exists more or less because this API was easy back in the old days of Unix. Input validation, unsound assumptions, relying on magic values (NULL termination) Boring stuff still causes damage
- phicoh 3y agoClassic array bounds error. Obviously, if argc == 0, you should not dereference anything in argv. It also shows why C is such a hard programming language. There is nothing to help the programmer in this case. The compiler doesn't know that argc is the length of argv, so there is not even a warning if you do it wrong.
- bloak 3y ago"Obviously, if argc == 0, you should not dereference anything in argv." I'd say that's not obvious. Normally argv is null-terminated so you can read argv[argc], and it's zero. There's no need to read argv[argc] if you've already looked at argc but sometimes it's more convenient to rely on the sentinel value.
- phicoh 3y agoIn my opinion that is like saying it might more convenient to use sprintf. Yes, that null has to be there for historical reasons. It doesn't mean it is a good idea to write code that relies on it.
- bloak 3y agoIt seems that (some version of) the C standard contains these words: argv[argc] shall be a null pointer So I wouldn't feel too bad about relying on it.
- lelanthran 3y ago> Yes, that null has to be there for historical reasons. It doesn't mean it is a good idea to write code that relies on it. That null has to be there because if it isn't then the implementation is broken, not the program. If you are coding defensively because the implementation might be broken, then you can't rely on argc being positive either
- planede 3y ago> Obviously, if argc == 0, you should not dereference anything in argv. Quoting ISO C17: > argv[argc] shall be a null pointer. Not constrained on argc being greater than zero (unlike the points that follow). So argv should always be dereferenceable within the program. It's an other question if this should be enforced by the Linux kernel or the C runtime. https://cigix.me/c17#5.1.2.2.1.p2 https://cigix.me/c17#5.1.2.2.1.p2
- JdeBP 3y agoYou are looking at the wrong standard. Look at the one for the operating systems' APIs.
- planede 3y agoIf you are writing a C program, then you should consult the C standard, and possibly POSIX. The Linux kernel + C runtime + C compiler should form a conforming implementation of the C language. Now if the Linux kernel doesn't promise to pass a non-NULL argv (and documents so), then it should be the responsibility of the C runtime to fix that once the program reaches "main". But as I wrote earlier, it's a whole other question.
- JdeBP 3y agoSo now go and actually consult the POSIX standard, as I just said. You clearly haven't.
- masfuerte 3y agoSo I just looked at the POSIX standard for the exec functions. It says the argv array is terminated by a null pointer when it arrives at the C main function. But a null argv doesn't cause exec to fail. Which implies that the OS or C runtime must fix it up between exec and main.
- planede 3y ago
- teo_zero 3y agoBut you don't have argc, only argv. The C stub will derive argc by traversing argv and then pass both to main().
- mort96 3y agoWait what bugs are caused by argv's null termination? Aren't all these bugs caused by the fact that argc can be 0 and programs don't expect that, not that argv is null terminated?
- hardware2win 3y agoIt is ugly as hell Whenever I see >while char != NULL Im a little bit sad. It doesnt feel robust.
- josefx 3y agoRelying on argc instead of null termination is a bit more robust since the check will still work even if you skip entries for any reason.
- mort96 3y agoI agree that using argc is better than null termination, but I don't understand how any of the bugs related to argc==0 are about null termination. Note that the buggy code behind CVE-2021-4034 literally uses argc, not null termination.
- kpcyrd 3y agoIn modern programming you would reject these arguments as invalid and refuse to perform the execve syscall with something like EINVAL. Instead we now have this additional quirk that's going to be part of the Linux lore forever and people may start relying on as a feature.
- davipb 3y agoAt the end of the day, any system used in the real world is going to have "lore", odd quirks that only exist because of historical reasons. Sure we can try to prevent it with good programming practices, but some things are usually only obvious in hindsight, and the real world is messy.
- deleted 3y ago[deleted]
- nikanj 3y agoIn modern programming: Someone a dozen dependency-of-dependency-of-dependency steps removed from you reads an RFC tome they found under the sofa, promptly changes the API to return EINVAL for a call that previously succeeded and breaks half the world. But at least it's modern, quirk-free and pure of thought.
- mhh__ 3y agoI think this happened recently on macos: the libc string parsing functions started complying with the C spec and broke some code (so you now need to check errno and the return value if you want to know the result was valid or not) I don't think it was deliberate, I suspect they may have switched libc or something.
- ilyt 3y agoIt's all easy if you can throw backward compatibility away after every release and tell users to fuck themselves.
- 3y ago
- JdeBP 3y agoThere are three things I expect to find in mailing list discussions before going off and reading them: * Someone suggests that the kernel return EINVAL for null or zero-length argument vector. Someone else then comes up with a mad but very real mainstream program that relies upon the system call succeeding. * Someone points out that the SUS requires that argv[0] be non-null. Someone else tries to weasel a difference between "shall" and "should", overlooking the SUS rationale that the leeway given is in the string contents, not that it is permitted to be outright null. * Someone suggests in all seriousness that this behaviour be retained for historical compatibility. For reference: * FreeBSD just returns EINVAL for a zero-length argument vector, https://github.com/freebsd/freebsd-src/commit/773fa8cd136a5775241c3e3a70f1997633ebeedf https://github.com/freebsd/freebsd-src/commit/773fa8cd136a57... . This came from OpenBSD. * A null argument vector has been EFAULT in FreeBSD since 2004, when someone noticed that the manual disallowed this, https://github.com/freebsd/freebsd-src/commit/7700eb86e7740ce5ea0180c6c39e11e816f265c4 https://github.com/freebsd/freebsd-src/commit/7700eb86e7740c... . * DragonFly BSD has been fixing up a zero-length argument vector by adding in a dummy non-null argv[0] since 2005, https://github.com/DragonFlyBSD/DragonFlyBSD/commit/66be65667b8038877f005ef4a11e4ee900928137 https://github.com/DragonFlyBSD/DragonFlyBSD/commit/66be6566... . It introduced EFAULT for a null argument vector at the same time. * Illumos has returned EFAULT for a null argument vector since at least the point when it went open-source in 2005.
- nikanj 3y agoThe last point ("we don't break userspace") has always been a core tenet of Linux. Linus essentially refuses to alter kernel behaviour just to be "correct" according to POSIX lawyers
- JdeBP 3y agoNo, that would be the first point. The last point is retaining the status quo simply for the sake of it, which sometimes happens but quite often does not, or out of a misinformed idea of what historically has actually been the case, which happens sometimes too. It's worth noting that being correct according to the SUS would have avoided the problem, since the SUS calls out the applications-code assumptions that cause the vulnerabilities in its rationale.
- jesprenj 3y agoIsn't Linux severely breaking the userspace with this change? What about programs that expect argc to be 0 or argv or argv[0] to be NULL?
- JdeBP 3y agoThey haven't been portable to other operating systems, and have been wrong, for at least 18 years at this point. They will have hit EFAULT or EINVAL on other operating systems. You can see the commit where Matthew Dillon introduced EFAULT and an argv[0] bodge into DragonFly BSD in 2005, for example, on this very page.
- ilyt 3y agoWell, when every possible change would cause something to be wrong you have to pick your poison.
- corbet 3y agoSee also: LWN's coverage from last year: https://lwn.net/Articles/882799/ https://lwn.net/Articles/882799/
- tyingq 3y agoSeems it's come up before: https://bugzilla.kernel.org/show_bug.cgi?id=8408 https://bugzilla.kernel.org/show_bug.cgi?id=8408 Rejected as "documented" in 2007. Also mentions a similar issue with envp.