11 ms·
“(null)” is a bad idea
- vortico 11y agoIn the context of where this would be a bad idea (e.g. flight control, rescue rover, etc), you would not use `printf()`, or perhaps any standard output at all. If anything, you will use lower level `write()`, where EFAULT or something would be raised on NULL, and/or static buffers, where NULL is impossible. `printf()` is really only good for developer-friendly programming, where absolute rigor is not the focus.
- cperciva 11y ago`printf()` is really only good for developer-friendly programming, where absolute rigor is not the focus. printf gets used all the time in systems code. I count 1221 occurrences of printf (and friends) in BIND 9 alone. More to the point, "(null)" was just an example of a debugging-hostile pro-vulnerability attempt to silently "fix" bad data.
- jeremiep 11y agoBIND 9 doesn't have the same requirements as flight control software. Nobody will die if BIND segfaults, at least I hope not. The software running in your car, plane, spacecraft and the likes have very strict regulations and you will find it radically different from systems code found in a linux distribution, even if written in the same language! See http://lars-lab.jpl.nasa.gov/JPL_Coding_Standard_C.pdf http://lars-lab.jpl.nasa.gov/JPL_Coding_Standard_C.pdf for more details. I wouldn't want to write code like this as it sounds very unproductive, but their reliability and astronomically small number of bugs speaks for itself.
- cperciva 11y agoMaybe we're just disagreeing about the scope of "developer-friendly programming".
- jeremiep 11y agoI was talking in the scope of the grand-parent, which mentioned flight control and rescue rover software. These environments don't care much for developer-friendly programming the way linux system utilities do.
- setpatchaddress 11y agoBIND is important internet infrastructure. We'd all be better off if it did follow the JPL standard.
- jeremiep 11y agoImportant yes but BIND can also afford having crashed nodes without the internet coming down to its knees. I'd be surprised if such a node came down and caused damages in the hundred of millions of dollars in the process. This is the distinction I wanted to make. I can't remember the last time my DNS didn't work. The internet can afford to be 'good enough', life critical software can't.
- deleted 11y ago[deleted]
- setpatchaddress 11y agoVenerable Unix utilities are generally not paragons of good modern systems programming practice.
- SamReidHughes 11y agoWith compiler-checked format strings (the norm today) printf is just fine.
- tbrownaw 11y agoanti-pattern known sometimes as "defensive programming": If something goes wrong, pretend you didn't notice and try to keep going anyway No. Defensive programming means, be predictable even in the face of nonsense. Return an error code instead of segfaulting, define reasonable non-error behavior for thing that don't have to be errors, etc. Sweeping things under the rug just means they'll come back to bite you later, which isn't defensive at all. and when faced by a choice between making the presence of a bug immediately obvious or trying to hide the bug and potentially exposing serious security vulnerabilities... well, I know which I would prefer This over a feature that makes "printf debugging" more usable? Is he smoking something? If the security of your code relies on a parameter not being null, you'd be a fucking idiot to let "printf() should segfault" be the way you catch that. Check it explicitly; not only is it safer but it makes broken callers easier to debug.
- cperciva 11y agoSweeping things under the rug just means they'll come back to bite you later, which isn't defensive at all. I did say "known sometimes as". And I've seen a lot of "check for NULL and silently replace it by something reasonable so that bugs don't make us crash" code justified as being "defensive". If the security of your code relies on a parameter not being null That's not what I said. Exploit paths may however result in pointers being NULL which are normally non-NULL, and so replacing NULL by "(null)" may turn a denial of service into something worse. Check it explicitly I absolutely agree, developers should use more assertions.
- tbrownaw 11y agoThat's not what I said. Exploit paths may however result in pointers being NULL which are normally non-NULL, and so replacing NULL by "(null)" may turn a denial of service into something worse. Exploit paths will likely cause variables to have values outside their expected range. Therefore library functions should be defined as narrowly as possible, in order to convert as many as possible of these cases into crashes. Never mind that this makes the library functions less useful. Never mind that this will push complexity into the caller (or just flat-out add complexity in some cases). Complexity which will necessarily add more opportunities for bugs.
- ikeboy 11y agoSo is "is a bad idea" replacing "considered harmful"?
- cperciva 11y agoHuh. Somehow the words "considered harmful" never even occurred to me while I was writing that.
- rebekah-aimee 11y agoThe phrase "considered harmful" is considered harmful, remember? http://www.catb.org/jargon/html/C/considered-harmful.html http://www.catb.org/jargon/html/C/considered-harmful.html
- millstone 11y agoI often use printf for debugging. An unexpectedly NULL string is exactly the sort of thing I want it to help me debug!
- cperciva 11y agoCrashing works just as well as printing "(null)" for that purpose. Possibly even better, since you'll have a core dump. ;-)
- millstone 11y agoSometimes it does, but often it does not: printf("name: %s %s", first, last); If it crashes, there's no easy way to know which one was NULL!
- cperciva 11y agoTell your debugger to open the core dump and print the two pointers. ;-)
- SomeCallMeTim 11y agoJust try that when debugging an app on an embedded system, mobile, or a console, where you have access to the console but no core dump. Sorry, but I agree with all the folks who say printf (and similar) is categorically more useful when it prints "(null)". I've had many obscure crashes on systems that were precisely because a logging printf in a seldom-tested piece of code had a parameter that sometimes was correctly null would crash it out. If the debugger isn't attached and we just have a crash dump we can't read (happens even on platforms where you're SUPPOSED to be able to read them), then we have to attach the debugger and hope that we can reproduce the obscure state that caused the crash. Anything that makes debugging statements more robust and less crash-prone is a good thing. End of story. As others have said, if you want something to verify that a pointer is non-null, use an assert().
- 11y ago
- kazinator 11y agoThis is nonsense. There is no reason why printf has to be the sacrificial function that blows up on null. Those same "misguided" operating systems run on hardware that traps null dereferences via an unmapped page. A predictable, tidy crash is just around the corner if that same is used in the program. On the other hand, if that printf is the only place the pointer is used, then there is no security hole. It's just undefined behavior on ISO paper, and you don't agree with how it is locally defined by the implementation. How do we know it's a security hole? Maybe some structure has a "char * " name field, and null indicates "anonymous/has no name". It's an ISO C conformance bug to try to print that with %s, but that's a portability issue. If that's the only problem in the program (everywhere else it checks for null), there is no real issue. POSIX and C libraries are not entirely about allowing null pointers or not. For instance gettimeofday lets you pass a null pointer for the "struct timezone * " second argument, which indicates that you aren't interested in specifying or retrieving it. On the other hand, a null argument is undefined behavior in getgroups, even if the array size is specified as zero (meaning, return me the size of the array actually needed and I will call you again soon with such an array)! A non-null pointer must be passed as a POSIX-conforming eggcorn. According to the logic of this article, if I pass null I'm committing a security hole, and an OS which allows getgroups(0, NULL) is misguidedly sweeping it under the rug. Null pointers are not some "bogey man". I built a Lisp dialect in whose implementation a C null pointer represents the nil symbol (empty list, false, ...). It prints as "nil", satsifies the symbolp function and so fort. Lexical variables are initialized to nil if not given an initial expression. Some C library printing a null pointer as (null) seems like nothing by comparison. Be that all as it may, you cannot actually fix this perceived issue by changing from (null) to crash, cold turkey. Unknown numbers of nonportable programs out there (otherwise well-tested programs) which rely on this will just crash. I just did a "grep -r -F '(null)' /var/log" on a system here and found a few hits. Most of those are probably via the %s conversion, and all that code would have to be fixed if printf were to assert this. And that's just instances confined to logging, not all output everywhere.
- cperciva 11y agoThere is no reason why printf has to be the sacrificial function that blows up on null. Of course not. Every function which has undefined behaviour upon being passed NULL should blow up on NULL. In general, anything which can crash should crash, because that's the fastest way to make sure that bugs get found.
- linkydinkandyou 11y agoI think it's a Bad Idea in C/C++, but it may not be a Bad Idea in some untyped interpreted languages, or in a special "checked build / debug" C++ environment.
- jeffdavis 11y ago"This is an example of an anti-pattern known sometimes as "defensive programming": If something goes wrong, pretend you didn't notice and try to keep going anyway." That's why SQL NULL is also a bad idea.
- tytron 11y agoHow else would you represent missing data in SQL than using nulls?
- wvenable 11y agoSQL NULLs propagate; you can't get an answer from a NULL unless you explicitly plan for it.
- nitrogen 11y agoIt can be a bit counterintuitive that "([NULL] < x)" and "NOT ([NULL] < x)" both return NULL in SQL. MariaDB [(none)]> select null as a union all select 1 as a; +------+ | a | +------+ | NULL | | 1 | +------+ 2 rows in set (0.01 sec) MariaDB [(none)]> select * from (select null as a union all select 1 as a) T where a >= 1; +------+ | a | +------+ | 1 | +------+ 1 row in set (0.00 sec) MariaDB [(none)]> select * from (select null as a union all select 1 as a) T where NOT a >= 1; Empty set (0.00 sec)
- jeffdavis 11y agoThat's false. SELECT SUM(x) FROM foo; If some x values are NULL and others not, you will never know.
- __david__ 11y agoHaving "%s" print "(null)" is perfectly fine for debugging and actually makes your life a lot easier since seeing "(null)" in your output is much quicker than popping into the debugger trying to figure out what line caused a segfault. (Yes, you can do it with a ternery but writing rigorous debug code is never on anyone's list). Having "%s" print "(null)" is absolutely useless in the meat of your string manipulation code. It's pretty much guaranteed to be a bug and could easily mask a problem (as described in the article). So I think the real problem here is that printf() is used for both debugging and for non-debugging string manipulation (sprintf() and friends). It's trying to serve two masters and it can never get both cases right. Maybe we should make "%s" not check for null and add a null-able "%S" for debugging.
- pjtr 11y ago"If you accidentally pass a null pointer as the argument for a ‘%s’ conversion, the GNU C Library prints it as ‘(null)’. We think this is more useful than crashing. But it’s not good practice to pass a null argument intentionally." https://www.gnu.org/software/libc/manual/html_node/Other-Output-Conversions.html https://www.gnu.org/software/libc/manual/html_node/Other-Out... It's documented.
- cperciva 11y agoThere are more C libraries than just GNU C...
- kazinator 11y agoIf there weren't, we wouldn't need a library clause in the ISO C standard to know what is portable among them. That doesn't add up to an argument that libraries shouldn't have documented extensions in areas left undefined by the standard.
- kazinator 11y agoFrom BSD errno manpage: 14 EFAULT Bad address. The system detected an invalid address in attempting to use an argument of a call. Is that an example of a misguided operating system? The kernel should kill the offending process instead of sweeping the bug under the rug with an errno code that might not be examined. Those who live in glass houses ...
- the_why_of_y 11y agoI have not checked which syscall or libc function could return that, but it's quite possible that some applicable standard like C or POSIX or SUS mandates such a return value. It is better to follow the standard in such cases, so that portable code written against the standard runs nicely. However (claims the OP) in the case of printf, the %s null pointer case is explicitly defined to be undefined behavior and hence the implementation should handle this either implicitly (by crashing with null pointer dereference) or explicitly by calling abort(), as that will teach programmers not to rely on superficially benign implementations of undefined behavior.
- kazinator 11y ago> mandates such a return value. And ISO and IEEE standards can't be "misguided", just competing operating systems that are more successful. > explicitly defined to be undefined behavior Contradiction. Undefined behavior is not defined, explicitly or otherwise. There can be explicit wording that some requirement is not being made. When ISO C explicitly states that there is an absence of a requirement, that does not carry any more or less weight then when behavior is undefined by omission of any wording of a requirement. All UB is equal. > not to rely on superficially benign implementations of undefined behavior. An excellent example of beneficially fixing undefined behavior is to implement strict left to right ordering on all operations and their side effects. A language or language dialect in which evaluation order is defined is safer and better for engineering.
- the_why_of_y 11y ago> And ISO and IEEE standards can't be "misguided" That's not a claim that I would ever make. But the right time to bring up objections is during the drafting of the standard. If you aim to implement a standard you should have a very good reason before you implement some interface differently than specified. I would argue that these return values are not a good enough reason. gets(3) on the other hand would hardly be missed... > Undefined behavior is not defined, explicitly or otherwise. There can be explicit wording that some requirement is not being made. In C, undefined behavior has a quite specific meaning: invoking undefined behavior is an error in the program and the implementation is at liberty to do anything it pleases. "undefined behavior: behavior upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements." Your example of evaluation order is not undefined behavior, but unspecified behavior: there is a set of possible orderings and the implementation may pick any one of them.
- gpvos 11y ago"on some misguided operating systems" — on which well-thinking systems does printf crash in this case?