Live data from Hacker News

Clang vs. Clang

blog.cr.yp.to

301–310 of 405 posts

Re: Clang vs. Clang

#301

Earlier quoted context omitted.

Deleting null pointer checks in the Linux kernel is the first one to come to mind

That's one CVE, right? How many other vulnerabilities were caused by compiler optimizations, whether they were bugs in the compiler or allowed by the spec?

You can probably enumerate them by searching for GCC compiler flags in the corresponding bug tracker. Start with ftrapv and fno-strict-aliasing. Those diverge-from-c flags exist to make code slower in exchange for not being broken.

Re: Clang vs. Clang

#302

Earlier quoted context omitted.

> it's the C standard that is under specified to the author's liking Isn't this unreasonable? Here we are, 52, years down the road with C et al. and suddenly it's expected that compiler developers must consider any change in the light of timing attacks? At what point do such new expectations grind compiler development to a halt? What standard would a compiler developer refer to to stay between the lines? My instincts…

We had ~30 years of "undefined behaviour" practically meaning "do whatever the CPU does". It is not new that people want predictable behaviour, it simply wasn't a talking point as we already had it.

We have ill defined behaviour, implementation defined behaviour, erroneous behaviour, unspecified behaviour, undefined behaviour.

Undefined behaviour isn't exactly what most people think it is.

Re: Clang vs. Clang

#303
Let's consider this function:

  char* strappend(char const* input, size_t size) {
    char* ptr = malloc(size + 2);
    if (!ptr) return 0;
    memcpy(ptr, input, size);
    ptr[size] = 'a';
    ptr[size + 1] = 'b';
    return ptr;
  }
This function is undefined if size is SIZE_T_MAX.

Many pieces of code have these sorts of "bugs", but in practice no one cares, because the input required, while theoretically possible, physically is not.

Re: Clang vs. Clang

#304

Earlier quoted context omitted.

In my experience it is very easy to accidentally introduce iterator invalidation: it starts with calling a callback while iterating, add some layers of indirection, and eventually somebody will add some innocent looking code deep down the call stack which ends up mutating the collection while it's being iterated.

That is not UB. That is simply mutable data. The solution here is static analysis (Rust) or immutable persistent collections.

In the context of C++ and STL it is UB.

They are in the process of rewording such cases as erroneous instead of UB, but it will take time.

Re: Clang vs. Clang

#305
post #193

Earlier quoted context omitted.

> If foo is false in your code, then the behavior is completely defined. That's the point. If foo is false, both versions do the same thing. If foo is true, then it's undefined and it doesn't matter. Therefore, assume foo is false. Remove the branch.

Yes! This is exactly the point. It is undefined, so given that, it could do what the other branch does, so you can safely remove that branch. you get it, but a lot of other people don't understand just how undefined, undefined code is.

It could do what the other branch does, in theory.

But let me put it this way. If you only had the misbehaving_code(); line by itself, the compiler would rightly be called crazy and malicious if it compiled that to delete_data();

So maybe it's not reasonable to treat both branches as having the same behavior, even if you can.

Re: Clang vs. Clang

#306

Let's consider this function: char* strappend(char const* input, size_t size) { char* ptr = malloc(size + 2); if (!ptr) return 0; memcpy(ptr, input, size); ptr[size] = 'a'; ptr[size + 1] = 'b'; return ptr; } This function is undefined if size is SIZE_T_MAX. Many pieces of code have these sorts of "bugs", but in practice no one cares, because the input required, while theoretically possible, physically is not.

It does something unexpected if size is SIZE_T_MAX-1, too. And it's also undefined if input is null and size is zero, which seems more likely to surprise that function's author. This is because memcpy requires valid pointer arguments even if the size is zero.

In particular, this usage invokes UB:

  const char *input = "";
  size_t len = strlen(input);
  char *buf = malloc(len);  // may return null if len is zero
  if (len) memcpy(buf, input, len);
  char \*buf2 = strappend(buf, len);
(Edited for formatting.)

Re: Clang vs. Clang

#307

Earlier quoted context omitted.

The original code is not invalid, even by the standard. It's not even undefined behavior. It is perfectly well defined as equivalent to `return true` according to the standard, or it can be implemented in the more straightforward way (add one to a, compare the result with a, return the result of the comparison). Both are perfectly valid compilations of this code according to the standard. Both allow inlining the func…

I don't know what you're ranting on about. Functions have parameters. In the case of the previous function, it is not defined if its parameter is INT_MAX, but is defined for all other values of int. Having functions that are only valid on a subset of the domain defined by the types of their parameters is a commonplace thing, even outside of C. Yes, a compiler can deduce that a particular code path can be completely e…

The point is that a compiler can notice that one branch of your code leads to UB and elide the whole branch, even eliding code before the UB appears. The way this cascades is very hard to track and understand - in this case, the fact that stupid() is UB when called with INT_MAX makes foo() be UB when called with 0, which can cascade even more.

And no, this doesn't happen in any other commonly-used language. No other commonly-used language has this notion of UB, and certainly not this type of optimization based on deductions made from UB. A Java function that is not well defined over its entire input set will trigger an exception, not cause code calling it with the parameters it doesn't accept to be elided from the executable.

Finally, I should mention that the compiler is not even consistent in its application of this. The signed int overflow UB is not actually used to ellide this code path. But other types of UB, such as null pointer dereference, are.

Re: Clang vs. Clang

#308
post #95

Earlier quoted context omitted.

I think this is actually a mistake by the author since the rant is mostly focused on implementation defined behavior, not undefined. The examples they give are all perfectly valid code. The specific bugs they're talking about seem to be compiler optimizations that replace bit twiddling arithmetic into branches, which isn't a safe optimization if the bit twiddling happens in a cryptographic context because it opens th…

> it's the C standard that is under specified to the author's liking Isn't this unreasonable? Here we are, 52, years down the road with C et al. and suddenly it's expected that compiler developers must consider any change in the light of timing attacks? At what point do such new expectations grind compiler development to a halt? What standard would a compiler developer refer to to stay between the lines? My instincts…

> This all looks like an engineering purity spiral.

To get philosophical for a second, all of engineering is analyzing problems and synthesizing solutions. When faced with impossible problems or infinite solution space, we must constrain the problem domain and search space to find solutions that are physically and economically realizable.

That's why the answer to every one of your questions is, "it depends."

But at the top, yes, it's unreasonable. The C standard specifies the observable behavior of software in C. It does not (and cannot) specify the observable behavior of the hardware that evaluates that software. Since these behavior are architecture and application specific, it falls to other tools for the engineer to find solutions.

Simply put, it isn't the job of the C standard to solve these problems. C is not a specification of how a digital circuit evaluates object code. It is a specification of how a higher level language translates into that object code.

Re: Clang vs. Clang

#309
post #273
post #23

C and C++ are unsuitable for writing algorithms with constant-time guarantees. The standards have little to no notion of real time, and compilers don't offer additional guarantees as extensions. But blaming the compiler devs for this is just misguided.

What languages are suitable for writing algorithms with constant-time guarantees?

According to some comments under this submission, even x86 assembly isn't suitable, or only under specific circumstances that are generally not available in userspace.

Re: Clang vs. Clang

#310

Earlier quoted context omitted.

> he's a perfectionist, and he merely expects perfection from the rest of us as well. Nicely put, but at the end perfectionism is a flaw.

Not when computer security is concerned.

In all things, moderation. Security must be evaluated as a collection of tradeoffs -- privacy, usability, efficiency, etc. must be considered.

For example, you might suspect that the NSA has a better sieve than the public, and conclude that your RSA key needs to be a full terabyte*. We know that this isn't perfect, of course, but going much beyond that key length will prevent your recipient from decrypting the message in their lifetime.

* runtime estimates were not performed to arrive at this large and largely irrelevant number

Post reply on HN