• 3 Posts
  • 29 Comments
Joined 2 months ago
cake
Cake day: August 10th, 2026

help-circle






  • I think I misread what they were saying (or they phrased it poorly). They presumably mean arbitrary code execution: e.g the hacker has overwritten a function pointer and taken over the control flow rather than “the server has provided the executable code”.

    I feel the phrasing at least is weird, but if we assume the above then it’s just a standard UaF->code pointer corruption. Handling ASLR, varying code layout, etc is a fairly well understood problem space - assuming the right info leaks exist those mitigations can be fairly easily resolved, the mitigations are insufficient on their own, they just increase the number of concurrent bugs needed (but for experienced attackers they’re still just a minor step).


    score 1 · canonical





  • I don’t really understand the problem here because fairscan is under GPL and all Ente code appears to be AGPL, which is allowed. Ente also did this quite openly (the blog post says that they even contacted the author).

    Opensource is not public domain. It frustrates me how often people act like it is.

    The entire point of all of the open source licenses is that they are protected by copyright. Anyone can distribute a derivative work, but they can’t remove the original copyright owner, nor claim to be the copyright owner. Similarly they can’t relicense beyond what the original license permits, and even if they do, they still cannot remove the copyright information because the original author[s] have not rescinded their ownership of the code.

    But, yeah, Ente could be a bit more thankful and handling this more elegantly.

    What Ente did was steal open source code and claim ownership of code that was not theirs to own. They cannot claim ownership of derived code.


    score 1 · canonical




  • I thought some of UB was to deal with different CPU behavior.

    That’s what implementation defined behavior means.

    Let’s say you’re on a machine where integer overflow is defined as trapping. Because it is UB, the fact that it has defined behavior is irrelevant, their are no constraints on the compiler. For example, say you have code that does this:

    int foo(int a, int b) {
        return a + b;
    }
    ...
    int x = INT_MAX;
    int y = cond ? 0 : 1;
    foo(x, y);
    

    If cond is true, then a + b is INT_MAX + 1 which is UB, so cond must always be false.

    It’s really important to understand that the compiler is not detecting this directly, a lot of optimizations are constraint based, and the result of applying those constraints is that UB paths get dropped.

    I hate that compilers assume that. I know that programmers don’t intentionally want to invoke UB in C, but there are so damn many footguns in C that I seriously doubt any non-trivial C program is completely UB free.

    Yes, but for the majority of cases where this happens, the problem is not that the compiler is assuming UB operations can’t happen. The problem is that operations with well defined behavior are being labeled as UB. Most of the foot guns around “the compiler removed this code” boil down to “the developer wrote code around well defined behavior on the platform, but the spec says it is undefined”. The moment that implementation defined behavior stops being labeled as UB, many of those foot guns go away.


    score 1 · canonical


  • Classifying a call to realloc with a size of 0 as undefined behavior would allow POSIX to define the otherwise undefined behavior however they please.

    What posix says is irrelevant though. realloc is specified as part of the language, compilers are aware of the semantics, and the platform providing defined behavior is irrelevant. If the intent is to permit a platform to specified the contract for realloc, the solution is implementation defined or unspecified behavior, not to label it UB.

    Labeling it UB means that

    void *foo(void* ptr, int newsize) {
       if (!newsize) report("zero sized realloc")
       return realloc(ptr, newsize);
    }
    

    can be optimized to

    void *foo(void* ptr,  int newsize) {
       return realloc(ptr, newsize);
    }
    

    If the compiler discovers that newsize is zero, this can be compiled to

    _foo:
    _some_other_function:
       ...
    

    That’s literally the definition of what UB allows. The underlying implementation is completely irrelevant, by saying realloc(ptr, 0) is UB they are not saying “the implementation can change the contract for a zero sized realloc”, they are saying that the behavior of an implementation in response to a zero sized realloc is irrelevant.

    More realistically a compiler might choose to lower realloc(.., 0) or equivalently malloc(0) to a trap: it’s UB, so it is permitted to do so, and malloc(0) and realloc(…, 0) are generally considered bad things to do for a variety of reasons. So trapping when that happens means that the mistake is identified early (it is definitionally incorrect code), helping the developer fix it, and it helps the implementation by removing the need to support it. This applies even if the implementation intentionally supports it.

    You cannot have a definition of UB in which there are no constraints on how a compiler treats signed integer overflow, while also saying that their are constraints on how realloc(ptr, 0) is interpreted. If a standard wants to say “the implementation can define the behavior in this case”, the solution is to say it is implementation defined.


    score 1 · canonical


  • So we agree that UB has nothing to do with cross platform compatibility.

    But your justification for the use of UB in cases like this is an argument against any implementation defined behavior, and it brings an immediate rebuttal

    Any conforming and correct C program that avoids implementation defined behavior will behave identically across all platforms.

    There is absolutely no difference in the degree of difficulty in avoiding all UB and avoiding all IDB: the logic required to avoid invoking UB for any given operation is that same logic required to avoid IDB. The result of making an error is the same in the overwhelming majority of cases, except by labeling something UB the compiler can disregard the behavior that it applies in other places.

    There is not difference in cross platform compatibility: the same incompatible code exists in both cases, the only difference is that by claiming something to be UB, code that is correct with respect to the target will behave in accordance with that hardware, except for unpredictable occasions where it will not, and it is not possible for a developer to ever reason about whether or not that happens, because UB is a global, not local, property of the program.

    So I have some questions, and I’m curious about what you feel that answers are - obviously you’re under no obligation to respond, but I am curious. I’m just saying IDB here, but IDB and unspecified are in the same general bucket for the purpose of this thread.

    1. In what way does labeling something like integer overflow UB rather than IDB make writing portable code easier?
    2. In what way does labeling something UB rather than IDB make reasoning about code behavior easier?
    3. in what way does labeling something UB make the language better than it would be if it was instead labeled IDB?
    4. If the concern is actually cross platform compatibility, then why not simply define the behavior?

    score 1 · canonical


  • Undefined behavior exists for a number of reasons. It allows implementations to support extensions, manage interactions with hardware-based safety mechanisms

    No.

    No. No. No.

    UB has nothing to do with supporting different hardware behaviour. At all.

    Let’s take signed integer overflow. That is UB. This does not mean that you can write a + b < a as an overflow check if that’s the behaviour on your hardware. Being UB means that if you code does this, you code is wrong. It does not check for overflow, it either:

    • does not overflow, so no UB
    • or it is UB, which cannot occur in correct code, so can be assumed to not happen

    This is ostensibly useful for vectorizing loop induction variables, but I’m not super convinced.

    There are

    1. Defined behavior
    2. Unspecified behavior
    3. Implementation defined behavior
    4. Undefined behavior

    (1) is obvious. (2) and (3) are what support different implementations and hardware, etc. The behavior is still defined, the code that triggers them is correct (per the AM, you’re still free to have logic bugs), the compiler is constrained, it cannot assume it doesn’t exist.

    And UB is behavior that is invalid on the AM, and anything invalid on the AM can be assumed to not happen.

    (C++ also has Erroneous behavior, but that basically just exists because the standard wants to be able to say a common error is an error, but recognize the failure mode is so catastrophic that it requires the compiler to have safe-ish behavior)

    Another misconception is that the compiler is detecting and optimizing UB it finds. What actually happens is the compiler encounters some code, and assumes that UB does not happen. e.g lets look a different case of ub: null dereferences.

    void do_thing(int a) {
      precondition(a > 0);
      userid = a;
    }
    

    Can a call to this function set userid to 0? let’s look at that precondition call. It turns out to be a macro:

    #define precondition(x) if (!(x)) *(char*)0=0;
    

    From the compiler’s point of view, that macro means that if a < 0 a null dereference occurs. That is UB, so it can’t happen, and the branch disappears, and the precondition does nothing. This is not hypothetical: there was a period where dereferencing a nullptr was considered a valid way to force a trap. Then one day gcc started optimizing on the basis of it being UB, and a whole bunch of security bugs went from theoretical to real.

    And to be even more explicit about what UB permits:

    void do_thing(int a) {
      a = -1;
      precondition(a > 0);
      userid = a;
    }
    

    can call an arbitrary function.


    score 1 · canonical