C: A Technological Landmine
blog.expandrive.com
blog.expandrive.com
"Lacking a strong and expressive type system, C not only permits but encourages its programmers to sacrifice correctness, safety, robustness, testability, and maintainability in favor of some highly underdeveloped and ill-measured ideas about “performance”. Much of the infrastructure of the Internet is built out of this garbage."
and especially words like "garbage" only exposes the author as someone who doesn't know what he is writing about. (ok i could have used the shorter word "fool" here, but ..).
The "infrastructure of the internet" (including the underlying operating systems) is one of the domains in which C shines.
There is good reason that even today, large chunks of "infrastructure" code is written in C/C++.
"anybody who considers C for high-level application development at this point in history, is in a grievous state of sin"
With "high level" being conveniently undefined and without any examples, that statement means next to nothing.
What a terrible, ill thought out article.
The thread of execution is extremely easy to follow.
The only thing I would change if we could revisit the past is that I would add a string primitive to the language with a half decent set of string operators. That would have made my life a lot easier at some point in the past.
The funny thing is that most languages that people use that criticize 'C' are usually at the core levels written in C.
There is probably a good case to be made for the claim that Unix would not exist if it weren't for the C language.
The difference is that the people using these higher level languages only depend on a single set of maintainers who need to get the primitives right once instead of every random coder needing to manage buffers, garbage collecting unused memory, threading, and a host of other landmines on each and every project.
It's the high level language cruft that is a prime source of all the endless layering of glue on top of other glue that we're stuck with.
Less scripting, more binaries.
I cringe every time I install some minor system level package and I have to include perl, awk, php and python or some other combination of stuff.
When this keeps happening over and over again it is time to consider the possibility that the problem isn't the craftsman but the tool.
The advantage of using lots and lots of glue languages is that it forces the important bits to be loosely coupled, adds flexibility to the development and deployment process, and leads to systems that are easier to comprehend and reason about. The only things that should be opaque binaries are system libraries and VMs/runtimes. Everything else should be "glue".
I find it hard to conceive of a posix compliant OS written in a higher level language, especially because of the lack of deterministic behaviour when handling interrupts and allocating memory. C is mind numbingly simple at that level which is exactly why it is used in these situations.
But every language makes it possible to write insecure code, C has its own unique challenges:
It isn't that long ago that somebody managed to get an exploitable scenario out of UTF-8, it took me a long time looking at the code to see how it was even possible. In a higher level language that sort of thing is more difficult to achieve, that's for sure.
The JVM and MS CLR are largely in C++.
Debugging that took me more time than I care to remember, I hung that box so often that I hooked the reset switch up to a foot pedal :)
Other than that C has been pretty good to me in terms of showing where the bugs are. Post mortem debugging would usually give you the location of your crash and that plus a little bit of deduction will tell you what you're doing wrong.
Memory overwrites (and stack overwrites) are things that belong to a certain phase of coding where you are still learning the ropes and you have not yet mastered the idiomatic solutions that people will use to avoid such problems. Setjmp and longjmp are also an excellent source of hard to find bugs, as well as multi-threading (the multi-threading problems are not unique to C, some languages are simply better at doing that sort of thing, occam for instance).
I've heard C compared to a race car, it's great as long as you stay between the lines, take a turn too fast and you're heading straight for trouble. Experience (as in bumping your head a couple of times) will make sure that you know how far you can push it.
C is like any other powertool (think chainsaw or plasmacutter), you can easily get hurt but once you've mastered the tool it is quite amazing what you can do with it.
I got in to C after a long period of writing almost nothing but assembly language (x86, 68K and a bunch of 8 bitters) and I remember thinking of C as a very 'high level language'. My perspective has changed a bit since then...
Check out Oberon (both the language(s) and the operating system(s)) for a look at an alternate universe in which C doesn't exist and Unix never happened. And people are happy. And anything that can be done in C can be done. Although some mistakes are significantly more difficult to commit.
> C/C++
He was talking about C, not C++. C++ has a good type system and also makes it easy to avoid bounds checking problems.
I’ll admit that the post was a little ranty. It was quickly reworked from an internal email conversation, and it’s not intended to be a taken as if it came from a conference proceeding.
With that in mind, do you deny that C/C++ “sacrifice correctness, safety, robustness, testability, and maintainability in favor of… ‘performance’”?
Large swathes of the internet have also had buffer overflow after buffer overflow after buffer overflow after buffer overflow after buffer overflow after buffer overflow after buffer overflow after buffer overflow after buffer overflow after buffer overflow after buffer overflow after buffer overflow after buffer overflow... for decades.
At some point you have to stop blaming "lazy" programmers and start pointing fingers at the language that let the same bug through thousands upon thousands of times; a bug whose term almost has no meaning in other languages. How do you "buffer overflow" Java or O'Caml? (It isn't quite impossible, necessarily, but it's multiple orders of magnitude less likely.)
A safe variant of C wouldn't have been that hard to create, but the culture wouldn't permit it. So... I also point fingers at the culture that has valued performance over correctness... not just highly, not just valued performance highly along with correctness, but has valued performance over correctness.
Anyway, the whole "the internet is written in C" is a very weak argument in C's favor; it certainly fit a culture and can't be argued to be totally impractical, but a lot of the sharp edges aren't actually necessary, even with performance as a primary goal, and those sharp edges have claimed far too many victims. I totally respect the original authors, but it should have been incrementally improved into something much safer, instead of freezing in its 1970s level of development.
C is a non-language. It's a tool used to give developers complete control of the hardware. It's just one step up from Assembler (to make the developers job a little bit easier).
Adding type checking, buffer overflow mitigation, or any other extraneous domain-specific feature is completely outside scope of the language. If you need those features, write a language, dialect, or compiler extension which supports them.
Anyhow, I seriously object to calling "buffer overflow mitigation" a "domain specific feature" since there's hardly a domain that hasn't been hammered by it being missing.
Furthermore, obviously the C that is not C is not the true C. I'm not trying to play semantics. My point is that "not the true C" should have been written decades ago and completely replaced C swiftly. There's an old saying about how you can make any program run quickly if there's no requirement for it to be correct. I don't care how small your processor is or how special your embeddedness makes you; ample evidence says that letting input data segfault your or worse, get executed as code, is a real and pressing problem, vs. the almost-always hypothetical problems of a little bit of overhead when dealing with buffers (to do it properly, no less). Start with correct code.
By the way, as I use this word "correct" I'm feeling a bit like an academic wonk, but bear in mind that I'm not talking "provable" or anything. I'm talking about making tens of thousands of critical security bugs over the past several decades go away, with programmers actually spending less time on bugs, too. It's not exactly a hypothetical consideration.
Numeric computing, for one.
This type of wrong assumption can be made in any language. And this bug doesn't make buffer overflows or any other exploitable activities possible, so they are not dangerous in a way that 'usual' C code can be dangerous.
Still it is a serious threat regarding the widespread usage of that particular library.
OSHA's rules make it much more expensive and tedious for American cities to grow - but the growth isn't coming on the backs of construction workers. It's a trade off we've decided to make because we value safety and we value not getting our pants sued off for negligence.
You can write some well designed quick-and-dirty C code that does what you want, and does it fast. But once in a while you'll make a mistake that you probably won't notice and might cost you your company.
"Marlinspike said since there is no legitimate reason for a null character to be in a domain name, it’s a mystery why Certificate Authorities accept them in a name."
edit: Furthermore, it's low-levelness makes it very versatile. It centers around the universal abstractions used in Unix - the ability to open, read, write, and close files. That, combined with structs, unions, and it's basic data types allow you to use it for virtually ANY protocol.
For an example of modern high-level C, check out GTK+. It's a sprawling cross-platform GUI library that provides the foundation for the arguably most popular Linux desktop environment (GNOME). Although GNOME apps are frequently written in higher-level languages using bindings, GTK+ itself is plain C.
Sadly, when you use C to implement low-level binary wire protocols, you quickly discover that structs, bit-fields, and unions are nearly useless because they are incompletely defined. Byte order is undefined. Structure layout is mostly undefined -- you pick field order, but you can't choose packing, alignment, or padding rules. The sizes of the integer types vary by platform and compiler. Bit field layout, packing, and alignment are almost completely undefined.
What you're left to work with are unsigned characters, pointers, and bitwise operations. You have to pack and unpack everything manually, or your code won't port. It's enough to get the job done, but it's like using a wrench to pound in screws.
I could do with a little less specificity of action, myself.
Think of it as compression, where a basic knowledge of computing is assumed. More interesting articles would have a lower compression ratio, which might be a fun thing to filter on. This article might go down to, "C is generally unsafe, and you probably aren't skilled enough to make it safe, so don't use it". Or maybe, "I needed to write something for my company blog, so I found a recent security hole and added some vaguely related platitudes".
If you start having type checking and various other easy-to-code and child-safety features, you are bloating and giving up performance in the low level libraries, if this happens imagine what the performance on the higher up application level would be.
They also wear cut-resistant gloves.
Hardware is always getting cheaper.
Losing data integrity and the trust/confidence of your users is extremely expensive, and can be fatal for a startup.
Performance is not the most important metric for a lot of applications.
I'd prefer the safe but slow 5 star crash test rated sedan with a good alarm over the race car that is going to blow up after a few races, in library terms.
This is a poor justification. A few years ago a house was a good investment because 'housing prices will always be going up.'
See, for example: http://blogs.wsj.com/informedreader/2007/05/25/a-metal-scare... which talks primarily about LCD displays (we're fast running out of Gallium/Hafnium/Indium), but points out that copper is likely to get significantly more expensive throughout this century.
That's going to increase the price/performance ratio of practically everything.
At the moment, both those prices are strongly dominated by the cost of processing, I would imagine. At some point, it could well be the scarcity you're paying for with copper, though.
Scarcity increases price, and two orders of magnitude is hardly inconceivable.
Then don't use C.
People don't seem to understand that C is just a step up from Assembly Language. There are no "types" in the sense that they exist in higher level languages. And thus, there are no ways to check those types (unless you want to write a type checking system yourself). Lets not forget that most modern programming languages with type checking and "rock solid secure" libraries were, in fact, written in C.
This is not a performance issue, only bad design and convention. (The other problem was using memcpy and strcmp on the same data. You can't treat blocks of memory as strings; a type system would eliminiate this confusion at no runtime cost.)
There are two groups you find misusing something. Those that really know what they are doing, have weighed the risks/rewards, and have decided that misusing the tool to get the job done is worth the associated risk. Then you have people who don't know what they are doing. They are going to have problems, but don't blame the tool.
unsigned long lsprintf(unsigned long max_length,char [asterisk]dest,char [asterisk]fmt, ...)
char buffer[1024];
lsprintf(1024,buffer,format);
Slightly overkill.