8 ms·
Aborting the program on integer overflow seems so drastic. Say you do that in a server program. Then convincing it to overflow an integer somewhere (possibly
by asveikau 6y ago
Aborting the program on integer overflow seems so drastic. Say you do that in a server program. Then convincing it to overflow an integer somewhere (possibly somewhere where it is harmless) causes denial of service and all unrelated connections in the process die?
I guess languages with support for exceptions have a more natural action for this, that could be caught somewhere in a server's processing loop to not affect other clients.
- deleted 6y ago[deleted]
- alblue 6y agoThis is essentially Swift’s problem on the server. If you have a service running and serving hundreds of clients, then any one of those threads can cause a trivial panic if there’s an overflow and take the entire service down. As a result, a number of use cases for swift on the server actually use heavyweight out of process threads (i.e. one process per client) so that the failure of one of them doesn’t take down the rest. Unfortunately there’s no way of overriding the panic (is implemented as an in-line ud2 instruction rather than an overridable function) and while you can install a signal handler to catch it, resuming isn’t straightforward and there are some cases where you really can’t do anything about it (malloc failure). This works great for single user devices where there’s only one app running at once and when it crashes the user restarts it, but doesn’t work in the server case unless you are using a cgi-bin/lambda pattern when each request is processed by a new process each time, which is what TF and the AWS lambda approaches do.
- saagarjha 6y agoI’m curious if one could use a mildly-modified Swift compiler that converts all the different ways to abort execution into some sort of panic handler you can override would help work around this issue.
- wahern 6y agoThis reminds me of Rust unconditionally ignoring SIGPIPE. Except in this case there's a non-trivial amount of pre-existing (and even future) code that assumes a process will be terminated if it attempts to write to a closed pipe. One of Rust's selling points is ease of FFI, so it's largely irrelevant that Rust APIs wouldn't let you ignore write errors. Rust breaks the environment for libraries that rely on the behavior.
- Joker_vD 6y agoIt's a bad library design to rely on a process-global setting, because one day you'll have two libraries in your project that rely on opposite values of such a global setting, and then you'll start to see very funny things. And the library design that relies on an application it's used in being killed by the OS so that the library don't have to tidy things up after itself or to properly handle error conditions is really bad. Don't you just like when one of your random indirect dependency, thrice-removed, decides to call std::abort() just because it's tired of living?
- wahern 6y agoI agree libraries shouldn't rely on process-global state. It's also bad library design to trigger signed overflow. In my libraries I try to handle both situations cleanly, but that's all beside the point. There's also the problem that SIG_IGN handlers are inherited across exec. So Rust programs that exec other programs without restoring the default handler could cause problems in programs where relying on SIGPIPE is more reasonable. This is also true of posix_spawn: SIG_IGN handlers aren't implicitly reset. Note that Rust doesn't restore any other handlers to their default disposition as far as I can tell. Do you write all your programs to reset signal disposition on startup? I'll admit I don't normally do that unless it's a daemon.
- Joker_vD 6y agoIf I were to write a program that relied on "writing to a file might terminate me and that's exactly what I want" behaviour, then yeah, I absolutely would include a call to "signal(SIGPIPE, SIG_DFL)" in the main(). Of course, if I were to actually write a filter-like program (a la cat, head, grep, etc.), I would rather have just checked the return values of my write()s. It's -1? Time to call exit(errno).
- galad87 6y agoThere is a compiler option to disable overflow/underflow checks, so unless the overflow is in some library outside your control, it's not actually an issue.
- MaxBarraclough 6y agoAs someone who doesn't know Swift, is there a reason it doesn't handle these errors by throwing exceptions? That approach works well for Java and .Net.
- ckwalsh 6y agoIn prod it's not a great idea, but in a test environment, perhaps handling simulated or shadow traffic, it's great.
- gpderetta 6y agoFWIW, division by zero already does the same thing.
- asveikau 6y agoAnd that's one reason that when I write code that divides and it's not known ahead of time, I usually check the denominator for zero. Doing that for every mathematical operation would not be feasible. Edit: hello good sir or madam downvoter, can someone explain why this is a controversial opinion? In languages where this error does not throw exceptions and in problem domains where the process really needs to stay up, reasonable caution would often mean you might manually check possible denominators for zero.
- MaxBarraclough 6y ago> Aborting the program on integer overflow seems so drastic. Disagree. Overflowing of a signed integer type is undefined behaviour, which deserves to be taken seriously. I was surprised to see that the article doesn't mention the signed/unsigned distinction, or undefined behaviour. Overflowing of a unsigned integer type is not a problem, it's defined to wrap around, and may be done intentionally.
- UncleMeat 6y ago> Overflowing of a unsigned integer type is not a problem, it's defined to wrap around, and may be done intentionally. It is not a problem for the language, but it may be a problem for your program. Unsigned overflow can happily introduce vulns into your bounds checks.
- scoutt 6y agoI guess people overreact to "undefined behavior". If your software runs always on the same platform then you can know what the behavior would be. It might also be intentional and should not crash your program. Undefined behavior is used when doing a specification for a compiler that will be used in many architectures and they cannot clearly specify what would happen on ALL architectures for a given operation, because the result might change among these architectures. Undefined behavior doesn't mean that your CPU has either a chance to suddenly blow away or that it would gain self-awareness and destroy the world. You can actually predict what would happen if you throw away a rock on earth. You cannot predict it for every planet in the universe, so theoretically "throwing a rock" produces undefined behavior.
- jjnoakes 6y ago> If your software runs always on the same platform then you can know what the behavior would be. This is not necessarily true, and I consider it to be a harmful misconception. I think people under-react to "undefined behavior". What you think the platform would do if you overflowed an integer using machine instructions and what your C or C++ code ends up executing can be wildly different things because C and C++ compilers do everything in their power to optimize your code assuming your signed integers will never overflow. If they do, who knows what state your program is in or which machine instructions the compiler would have scheduled.
- lmm 6y ago> Aborting the program on integer overflow seems so drastic. Say you do that in a server program. Then convincing it to overflow an integer somewhere (possibly somewhere where it is harmless) causes denial of service and all unrelated connections in the process die? If you don't abort then you probably have an arbitrary code execution vulnerability. Overflow is undefined behaviour in C++, your code is in an unanticipated state, a creative attacker can probably leverage that to do anything.
- higerordermap 6y agoHe is saying it should raise an exception which can be caught somewhere up the stack.
- asveikau 6y agoYes, but I would take minor issue in that I wasn't saying what should happen so much as what the options are given various tradeoffs. There are programming communities that have criticisms of exceptions and would like to avoid them. I am sympathetic to that. But if you do have exceptions, this is a good candidate. If you don't have exceptions, making integer overflow trap seems problematic. Posix signals may be another solution, with its own problems and ugliness.
- asveikau 6y ago> If you don't abort then you probably have an arbitrary code execution vulnerability. There are some patterns in which that is totally true, for example if an integer product is passed to malloc or realloc. Those cases tend to get extra scrutiny in security reviews because it is a well known class of issues. However, it is certainly not the case that every case is exploitable.
- lmm 6y agoNot every case is exploitable, but people are bad at reasoning about which cases are exploitable; a lot of DoS bugs that are initially thought to be harmless turn out to be exploitable. So I think it's important to treat any UB as exploitable until proven otherwise.
- amelius 6y agoPerhaps we can have both. E.g. __checked__ int a; // aborts on overflow __throwing_checked__ int b; // throws exception on overflow You could have the same syntax for unsigned ints.
- mhh__ 6y agoD has checkedint which does this cleanly in a template
- mhh__ 6y agoThat's the tradeoff at play here. Think of it like debugging a python program, it might fail hours after you start it but if it does fail it's failed for a reason (uncaught overflows can be very very not good)