Into the Depths of C: Elaborating the De Facto Standards [pdf]

10 years ago (cl.cam.ac.uk)

I've been using C for over 20 years and I'm sure I would be caught out by these...

...but...

The fact these curiosities are not an issue in day-to-day work and C is (one of) the most popular languages around today mean that they aren't too serious.

When you have a knowledge of the hardware and are working at that level day-in day-out then issues like this really don't bother you that much.

(I do realise that this is a slightly contrarian view these days, but there is an awful lot of unjustified C-bashing around currently).

  • http://cacm.acm.org/magazines/2016/3/198849-a-differential-a...

    8575 C or C++ packages in Wheezy. This tool found definite UB bugs in 40% of them.

    How would you know that your code is sometimes misbehaving because of UB?

  • > The fact these curiosities are not an issue in day-to-day work and C is (one of) the most popular languages around today mean that they aren't too serious.

    C became one of the most used languages, not necessary popular, thanks to the adoption of UNIX and the rise of FOSS/C culture (UNIX based) in the late 90's.

    Back then it was just yet another systems programming language.

    I only started to care about it when I moved from MS-DOS into Windows / UNIX, and even by then I was into C++ after a short (1 year) encounter with C.

    As for not being an issue, the CVE list shows daily the cost of any programming language that "enjoys" copy-paste compatibility with C semantics.

    Or the business opportunity for those that sell tools that help both developers (static analyzers) and users (anti-virus/firewalls) to overcome those shortcomings.

    • Late '90s is about a decade off, IMO. C was very much the only systems language by then.

      I think the last time a popular OS was built on something else than C was the original Mac OS, which had a Pascal API and used Pascal calling conventions.

      On the desktop, C was chosen as the API language for Windows and OS/2 around 1986. That meant both Microsoft and IBM agreed that PC software is going to be in written in C.

      8 replies →

  • Undefined behavior in the form of memory safety issues is a problem in day-to-day work.

    • UB and memory safety are a big issue exactly because they are not a problem in day to day work.

      I literally can't remember the last time I had spent any significant time investigating one of these issues. In my experience when that a crash happen (usually in a unit test or the first time you start the app) because of these issues, the backtrace points you to the exact problem.

      The pain start when the program and tests work correctly for all reasonable inputs and the underlying issue never manifests in during normal execution and can be potentially exploited by a malicious attacker with a carefully crafted input.

      What I'm trying to say is that I don't want memory safety because it would improve my daily programming experience (in fact possibly the reverse would be true), but I want it because I want security.

      2 replies →

    • But that's not what this study is about: everyone agrees that stomping wildly off the end of an array in C is not going to end well.

      This is about far more subtle issues than that - issues where there is some disagreement about whether it's OK to do or not. And the GP is right - these are often not such a problem in practice, if only because these are the kinds of issues where experienced C programmers know that they're sailing close to the wind, and there's almost always an alternative construct that's on more solid ground.

      2 replies →

This is interesting work that I was just looking at by following-up on who funds CakeML: a mathematically-verified ML implementation. Led to Cerberus, ISA's models (including RISC-V), concurrency analysis, POSIX API models/testing... all sorts of stuff. One of those rare programs doing something at every layer of stack with theoretical and practical contributions. Check them out here:

https://www.cl.cam.ac.uk/~pes20/rems/

Cerberus main page and links are here:

https://www.cl.cam.ac.uk/~pes20/cerberus/

Work like this will eventually, if not already, be applied to other projects along the lines of CompCert, seL4, and static analysis. The models of real-world assembly and C come first. Then, other tools map C to assembly or specs to C. So, this is pretty fundamental stuff they're working on. That they try not to abstract away the dark corners is the real advance here as many try to cheat. :)

Note to ingve: One of those Jung-style coincidences that you submitted this around exact time I wrote up same project for Schneier's blog. I've only looked at it twice in its existence. Odds were slim we think & write around same time. Always find it interesting when that happens.

If you think you know C quite well, here is one of the studies the authors ran to elaborate their semantics on corner cases of the language: http://www.cl.cam.ac.uk/~pes20/cerberus/notes50-survey-discu...

> If you zero all bytes of a struct and then write some of its members, do reads of the padding return zero? (e.g. for a bytewise CAS or hash of the struct, or to know that no security-relevant data has leaked into them.)

(and 14 other questions)

Webpage of the project: http://www.cl.cam.ac.uk/~pes20/cerberus/

  • I knew C quite well. Haven't written any for years. The statements "zero all bytes of a struct" and "reads of the padding" contain enough ambiguity that it answers the question. Not to mention the ambiguity in the words "read" and "write" as they pertain to C, since they already have a "std" meaning that's not the same as lvalue or rvalue, so what exactly do they mean here?

    And if you think you can answer the question without resolving the ambiguities, that answers some other questions.

    • I believe the questions in this study (I did not write it, only know the authors) were deliberately open-ended, allowing for comments on the specifics. A previous, much longer version used to contain code examples to comment, but it proved too detailed for people to complete.

      Moreover, the study was explicitly not about ISO C: "We were not asking what the ISO C standard permits, which is often more restrictive, or about obsolete or obscure hardware or compilers. We focussed on the behaviour of memory and pointers. This is a step towards an unambiguous and mathematically precise definition of the de facto standards: the C dialects that are actually used by systems programmers and implemented by mainstream compilers."

      Here is an actual example of a comment to this question:

          I would expect this code to work:
          
          struct foo
          {
              char a;
              double b;
          };
          
          foo p;
          foo q;
          memset( &p, 0, sizeof( p ) );
          memset( &q, 0, sizeof( q ) );
          p.a = 1;
          q.a = 1;
          assert( memcmp( &p, &q, sizeof( foo ) ) == 0 );

      3 replies →

To write portable code, I wouldn't study de-facto definitions of de-jure undefined behavior, except to see if I could cover every possible one and only if all alternatives were inferior.

  • Indeed not, but enquiring into what the in-the-wild de-facto beliefs about behaviour are might help in deciding what the de-jure rules should be changed to, or what a compiler implementation ought to do if it cares about what it does on the vast mass of code out there that does commit undefined behaviour, wittingly or otherwise...

    • Undefined behavior has a purpose: Not specifying implementation details makes it easier to write new implementations and for a wider variety of platforms. "De facto standards" take away this freedom, so ideally you'd want to reject reliance on UD, but I see your (second) point about that not always being practical. I guess "be conservative in what you do, be liberal in what you accept from others". Just make sure that your foundations are strong (pun) or the whole house will be an EcmaScript.