Stories
Slash Boxes
Comments

SoylentNews is people

posted by CoolHand on Friday May 08 2015, @09:01PM   Printer-friendly
from the off-with-its-head dept.

Ladies and gentlemen, the C programming language. It’s a classic. It is blindingly, quicksilver fast, because it’s about as close to the bone of the machine as you can get. It is time-tested and ubiquitous. And it is terrifyingly dangerous.

The author's biggest issue with the C language seems to be security holes:

If you write code in C, you have to be careful not to introduce subtle bugs that can turn into massive security holes — and as anyone who ever wrote software knows, you cannot be perfectly careful all of the time.

The author claims that the Rust language is a modern answer to these issues and should replace C (and C++). It does look that Rust can run C code, so it looks like an interesting proposition. What do Soylent's coders think about this?

 
This discussion has been archived. No new comments can be posted.
Display Options Threshold/Breakthrough Mark All as Read Mark All as Unread
The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.
  • (Score: 3, Informative) by tftp on Saturday May 09 2015, @06:35AM

    by tftp (806) on Saturday May 09 2015, @06:35AM (#180652) Homepage

    If you want to do a buffer overflow on my last C application, you'll have to enter it using a 4 button keypad and a menu on a 1602 LCD

    Sometimes it is enough to keep one button in the pressed state for an hour or two. Chewing gum will do it for you. Correct coding of the interrupt handler and the input queue takes time; time is always in short supply. Are you sure that the queue implementation in your RTOS, or in whatever code that runs in your main loop, is safe and that you are using it safely? Can you prove it?

    I did my share of C coding for AVR, and I had to reinvent all this code more than once. Sometimes I reused my old code. I cannot prove that all of it is safe. I follow best practices, though, and the code is functional. But can it be hacked via an intentional, never occuring in tests, buffer overflow? I cannot say. I just don't have manpower to check for all those "can't happen" situations. I can only say that the code was tested on datastreams that it was supposed to handle.

    Starting Score:    1  point
    Moderation   +2  
       Interesting=1, Informative=1, Total=2
    Extra 'Informative' Modifier   0  

    Total Score:   3  
  • (Score: 2) by VortexCortex on Saturday May 09 2015, @08:32AM

    by VortexCortex (4067) on Saturday May 09 2015, @08:32AM (#180685)

    I can only say that the code was tested on datastreams that it was supposed to handle.

    My meta language can output in C and generates unit test stubs with input fuzzing for numerics and strings based on parameter type or explicit doc comment ///@range[a..b,c,d..e] or ///@random[...]

    There really is no excuse for not testing your code properly if it has anything to do with security.

    • (Score: 2, Informative) by tftp on Saturday May 09 2015, @06:11PM

      by tftp (806) on Saturday May 09 2015, @06:11PM (#180821) Homepage

      My meta language can output in C and generates unit test stubs with input fuzzing for numerics and strings based on parameter type or explicit doc comment ///@range[a..b,c,d..e] or ///@random[...]

      This works great if this is a high level software. However in the examples that the AC and I used you need to generate those test vectors not only across the input space, but also across time. Most embedded systems have global variables. Usually they are protected with some sort of a mutex. Typically they are declared 'volatile.' This works fine in userspace. But it becomes funnier when you enter the land of interrupt handlers. Often you cannot wait on a mutex in an interrupt handler because then the code deadlocks. You sometimes use the {enable,disable}_interrupt() macro to ensure that the usercode touches the protected variables only when interrupt handlers can't... but this is an equivalent of Linux's BKL, and it is bad. What to do if you cannot use this crude "mutex"- say, if you have interrupts that must not be disabled, even for this little, or if you have several levels of interrupts and they can interrupt each other? Then it calls for a more complex interrupt handling, when interrupts of different levels can interrupt lower ones - and this allows to wait in interrupt handlers, as the timer runs on NMI or an equivalent.

      As an example, I have the Schwinn 470 Elliptical Machine. (A very useful thing for a programmer, by the way.) It has at least one bug. If you press the "PAUSE/END" button when it beeps, the incline level drops. I suspect that interrupts from the timer and from the GPIO are not on friendly terms with each other.

      This is not the only catch that you can find in embedded systems. There are other - like hardware that sends you values that are documented as "cannot happen, ever." Are you sure that you wasted precious bytes and microseconds in an interrupt handler, with interrupts disabled, to check for six impossible things? Yes, it is a prudent thing to do. But how many people have done it, after they received assurances from their hardware developers that this here CPLD of FPGA simply cannot output a byte with values outside of the 0x00 to 0x0F ? Are you using this value as an index into a 16-element array, by any chance?

      But of course an embedded programmer with 20 years of experience will spend a single AND machine instruction and make this value safe. Will a junior programmer with three weeks of experience do the same? Will his code be reviewed? Will the bug be found? It's pretty hard to look for bugs in code that is written in languages that have no explicit contract. That byte that we think is in the [0..0x0F] range may be latched in one place, transferred between different pieces of code and different globals, and may end up somewhere where you wouldn't be even checking. You'd have to verify that someone, somewhere ensures that the value is in range. A reviewer sees one module at a time. With tens of such variables, range enforced in various places (or nowhere,) only the code's developer can be reasonably aware of what exactly is happening. Not even him in case of a large codebase, or after a few months of working on another project. Design by contract is possible in C, but only by hand - poorly. Enums can have any int value. Packed structures may have different memory layout on different CPUs, or with different compiler options. Endianness is also somewhere there, lurking in the shadows and waiting to bite you when you are not vigilant. Incompatible types may be freely passed between modules, gaining or losing sign or bits. As memory in MCUs is not exactly infinite, you tend to use uint8 and uint16... but are they sufficient for the task at hand? Why do you think so? Say, the ticks counter will wrap after 240 days of continuous operation. Is this OK or not? What design requirement have you based your opinion on? Does the customer agree? If it is not OK, what have you done to ensure that the overflow is handled properly? Is there is a code somewhere, written by someone else, that checks for a too long execution time and throws an assert() if, when treated as uint32, it exceeds 0x00000FFF? Do you know that this particular runtime implements assertion failure as "while(1) {}" ? It's not an excuse that you never use asserts. A closed source library that your boss purchased may do so.

      As most, if not all of these checks can be done by compiler and by the runtime, it is usually beneficial to spend a bit of excess CPU performance on checking all these things and doing the most reasonable thing if the conditions are not satisfied. Doing it all by hand is possible, especially in C (where everything is possible,) but it increases code size, increases probability of a bug, and increases the cost - while doing nothing at all in normal use. What software manager will approve this work if the VP is breathing down his neck?