Date: Sun, 31 Aug 2025 15:12:00 -0700
> On Aug 31, 2025, at 5:27 AM, Marcin Jaczewski <marcinjaczewski86_at_[hidden]> wrote:
>
> niedz., 31 sie 2025 o 12:59 Oliver Hunt <oliver_at_[hidden] <mailto:oliver_at_[hidden]>> napisał(a):
>>
>>
>>
>> On Aug 31, 2025, at 3:18 AM, Marcin Jaczewski <marcinjaczewski86_at_[hidden]> wrote:
>>
>>
>>
>>> But there could be rare cases where exploit can't lead to privilege escalation.
>>> Image one off error in logging subsystem. You can override some parts of logs.
>>> As this is writeonly, a corrupted file does not affect the rest of the program.
>>
>>
>> There have been RCE vulnerabilities that were started by literally a single byte buffer overrun.
>>
>> There have been vulnerabilities that got to kernel compromise via calls to exit and abort.
>>
>
> You missed my point. Where I said it can't do that at all? I only give
> examples where this is not the case.
> Another situation like this would be to write to struct padding, in
> most programs impossible to observe.
The problem with this is that the failure to diagnose immediately means you have a potential security bug lying around, that is currently “not a security issue”.
In this case a write may not be “bad”, but a *read* might be.
The bigger concern I would have though is simply: you have a memory access bug, you could identify now *before* it is exploitable, or later on it may become exploitable - we’re talking about padding, so any number of changes, even “no functional change” changes, could now make the existing bug become exploitable.
e.g you are still better off terminating immediately rather than relying on the current apparently safe behavior (1) actually being safe, and (2) remaining safe in the future in the face of even the most innocuous changes. Using the horrendous “supply chain integrity” phrase: consider an open source project, while people are getting more cautious about new contributors or weird patches, anyone would accept a PR that was simply “improve padding/memory use of some_type” with no reason to think a simply struct layout change, or “packed” annotation, or similar could _possibly_ be an intentional injection of a security exploit..
> Simply put there are cases UB that can be benign in a specific
> environment (of course it could become cancerous after some trivial
> unrelated change). Something like this:
> ```
> int foo() {}
> int bar() { return 0; }
> ```
> This code will "work" on some compilers, assembly will be right but
> only as an accident.
UB is a whole different can of worms, if your impression has been that I am saying “terminate on UB”, that was not my intention and I apologize for not being clear.
Memory access violations and similar that are fundamentally UB, but there are numerous cases where it is entirely possible to prevent the error from occurring at all, bounds checks being one such (on containers, etc).
To my mind the problem of UB is _not_ “some UB introduces security problems”, it is “if the compiler proves UB can happen it can remove any amount of security checks, and it can ignore things like ’this has well defined behavior on the target’”, but this is fundamentally the result of overuse of “UB” for cases that should be unspecified or implementation defined. #OldManShakesFistAtClouds
>>
>> No, a guaranteed termination tells you the first point at which a crash could happen, vs miscellaneous crashes all over your service that you have no way to determine the origin of. Knowing the point of failure is far far easier to deal with than random crashes all over your codebase.
>>
>
> You again miss my point. This was from the attacker perspective. You
> know that request will cause memory override but not all will case
> service to crash or miss behave. Because there could be other checks
> that
> prevent attacks. Like you can send to long string to server that will
> be able to override the return pointer on the stack somewhere, but
> this string must be valid utf-8 (as this is the first thing that
> server does with a request).
I’m not sure what you are saying here - I have dealt with attacks in which the attacker intentionally constructs their attack to prevent anything from terminating or crashing.
From the attackers point of view, a code error that results in immediate deterministic program termination is not exploitable.
A code error that does not, is an exploitation opportunity. Sometimes the bug, simply by happenstance will unavoidably result in process termination for some other reason (not bounds checks, maybe the overflowed buffer always ends at a page boundary), but in that case the lack of compiler enforced termination has not introduced a difference in how DoSable the bug is.
>
> Another case like this is that stack array have "safe" buffer:
> ```
> Uint64 getUserPasswordHash()
> {
> std::array<char, 32> login = { };
> std::array<char, 32> pass = { };
> //... it was using `login` in safe way
> gets(pass.data()); //no limit! can override `login` but its not
> used here any more
> return hash(pass);
> }
> ```
> the value received `pass` does have a check but for utf code points
> not bytes, this means
> that you can only send 31 ASCII characters and this will be fine, but
> other scripts can exceed this limit.
> Users from Europe rarely exceed 40 bytes. And assembly here works
> "fine" but when an attacker uses easter asian script, he can override
> the return pointer.
> This means many requests are bugged but only a couple can crash the system.
> After hardening the majority of users will cause a crash here.
"You can override login but it’s not used anymore”
But the rest of the stack is, so the attacker compromises earlier frames, your return address, etc. Going back to “early termination on memory error causing crashes that don’t otherwise exist”: this kind of error is trivially able to crash the process just as much as a deterministic termination, only it also lets the attacker attempt to control the machine state, leak information, etc.
Again an immediate termination does not meaningfully change the ability of the attacker to cause your service to crash, only what other things they can do instead.
[what I would give for gets to be a link failure :D]
>
> I do not deny this is a good thing, but sometimes it would require
> temporary revert hardening
> until this bug gets fixed.
I have literally never heard of any case where a memory corruption bug was discovered, and the response was to remove the security checks that identified the bug. I would personally consider that unethical engineering, and I would assume that any security group in the company or project would be unbelievably angry if someone did this.
Ignoring just fixing the bug, mitigating the bug (early heuristics to lower the frequency), saying “we’re not sure how to deal with this immediately, lets just double the buffer size to reduce frequency”, etc are always options.
> This is something similar to C++ soft from
> '90 where its in many cases used
> UB and work fine, but on new compilers it crashes because of that UB.
Again, UB and adversarial compilers is a separate topic from this, that infuriates me for entirely different reasons :D
> I had similar problems when I added more validations to user configs
> in my program.
> Before it accepted contradicting configs but somehow it still worked.
> Even enforcing some basic sanity in configs break many uses and make
> them unhappy
> as it forces them to update things that from their perspective "worked
> fine before".
Assuming this is not a security issue - which mismatching configs do - there are many options available for dealing with this kind of issue. Commercial OS vendors have to do this for actual compiled code doing the wrong thing, and have many ways to do so. Raymond Chen posted about some application that had a bunch of memory overruns that caused it to crash when a new allocator was introduced, and they fixed that by simply increasing the allocation size when that or similar applications performed suspicious allocations.
In your case there would be plenty of options, from starting with a warning, or using side channels to see if the config likely dates to the older release, to (scary/gross) writing a comment back into the config saying “there is a problem here”.
But also just to be very clear: an identifiable “this is questionable” issue in a config file, is not the same as a memory access violation, unless that config issue can cause your program to get into an invalid state, that can in turn lead to exploitable issues (i.e if your program was a setuid binary, and the config confusion would allow an unprivileged user to perform a privileged operation).
—Oliver
>
> niedz., 31 sie 2025 o 12:59 Oliver Hunt <oliver_at_[hidden] <mailto:oliver_at_[hidden]>> napisał(a):
>>
>>
>>
>> On Aug 31, 2025, at 3:18 AM, Marcin Jaczewski <marcinjaczewski86_at_[hidden]> wrote:
>>
>>
>>
>>> But there could be rare cases where exploit can't lead to privilege escalation.
>>> Image one off error in logging subsystem. You can override some parts of logs.
>>> As this is writeonly, a corrupted file does not affect the rest of the program.
>>
>>
>> There have been RCE vulnerabilities that were started by literally a single byte buffer overrun.
>>
>> There have been vulnerabilities that got to kernel compromise via calls to exit and abort.
>>
>
> You missed my point. Where I said it can't do that at all? I only give
> examples where this is not the case.
> Another situation like this would be to write to struct padding, in
> most programs impossible to observe.
The problem with this is that the failure to diagnose immediately means you have a potential security bug lying around, that is currently “not a security issue”.
In this case a write may not be “bad”, but a *read* might be.
The bigger concern I would have though is simply: you have a memory access bug, you could identify now *before* it is exploitable, or later on it may become exploitable - we’re talking about padding, so any number of changes, even “no functional change” changes, could now make the existing bug become exploitable.
e.g you are still better off terminating immediately rather than relying on the current apparently safe behavior (1) actually being safe, and (2) remaining safe in the future in the face of even the most innocuous changes. Using the horrendous “supply chain integrity” phrase: consider an open source project, while people are getting more cautious about new contributors or weird patches, anyone would accept a PR that was simply “improve padding/memory use of some_type” with no reason to think a simply struct layout change, or “packed” annotation, or similar could _possibly_ be an intentional injection of a security exploit..
> Simply put there are cases UB that can be benign in a specific
> environment (of course it could become cancerous after some trivial
> unrelated change). Something like this:
> ```
> int foo() {}
> int bar() { return 0; }
> ```
> This code will "work" on some compilers, assembly will be right but
> only as an accident.
UB is a whole different can of worms, if your impression has been that I am saying “terminate on UB”, that was not my intention and I apologize for not being clear.
Memory access violations and similar that are fundamentally UB, but there are numerous cases where it is entirely possible to prevent the error from occurring at all, bounds checks being one such (on containers, etc).
To my mind the problem of UB is _not_ “some UB introduces security problems”, it is “if the compiler proves UB can happen it can remove any amount of security checks, and it can ignore things like ’this has well defined behavior on the target’”, but this is fundamentally the result of overuse of “UB” for cases that should be unspecified or implementation defined. #OldManShakesFistAtClouds
>>
>> No, a guaranteed termination tells you the first point at which a crash could happen, vs miscellaneous crashes all over your service that you have no way to determine the origin of. Knowing the point of failure is far far easier to deal with than random crashes all over your codebase.
>>
>
> You again miss my point. This was from the attacker perspective. You
> know that request will cause memory override but not all will case
> service to crash or miss behave. Because there could be other checks
> that
> prevent attacks. Like you can send to long string to server that will
> be able to override the return pointer on the stack somewhere, but
> this string must be valid utf-8 (as this is the first thing that
> server does with a request).
I’m not sure what you are saying here - I have dealt with attacks in which the attacker intentionally constructs their attack to prevent anything from terminating or crashing.
From the attackers point of view, a code error that results in immediate deterministic program termination is not exploitable.
A code error that does not, is an exploitation opportunity. Sometimes the bug, simply by happenstance will unavoidably result in process termination for some other reason (not bounds checks, maybe the overflowed buffer always ends at a page boundary), but in that case the lack of compiler enforced termination has not introduced a difference in how DoSable the bug is.
>
> Another case like this is that stack array have "safe" buffer:
> ```
> Uint64 getUserPasswordHash()
> {
> std::array<char, 32> login = { };
> std::array<char, 32> pass = { };
> //... it was using `login` in safe way
> gets(pass.data()); //no limit! can override `login` but its not
> used here any more
> return hash(pass);
> }
> ```
> the value received `pass` does have a check but for utf code points
> not bytes, this means
> that you can only send 31 ASCII characters and this will be fine, but
> other scripts can exceed this limit.
> Users from Europe rarely exceed 40 bytes. And assembly here works
> "fine" but when an attacker uses easter asian script, he can override
> the return pointer.
> This means many requests are bugged but only a couple can crash the system.
> After hardening the majority of users will cause a crash here.
"You can override login but it’s not used anymore”
But the rest of the stack is, so the attacker compromises earlier frames, your return address, etc. Going back to “early termination on memory error causing crashes that don’t otherwise exist”: this kind of error is trivially able to crash the process just as much as a deterministic termination, only it also lets the attacker attempt to control the machine state, leak information, etc.
Again an immediate termination does not meaningfully change the ability of the attacker to cause your service to crash, only what other things they can do instead.
[what I would give for gets to be a link failure :D]
>
> I do not deny this is a good thing, but sometimes it would require
> temporary revert hardening
> until this bug gets fixed.
I have literally never heard of any case where a memory corruption bug was discovered, and the response was to remove the security checks that identified the bug. I would personally consider that unethical engineering, and I would assume that any security group in the company or project would be unbelievably angry if someone did this.
Ignoring just fixing the bug, mitigating the bug (early heuristics to lower the frequency), saying “we’re not sure how to deal with this immediately, lets just double the buffer size to reduce frequency”, etc are always options.
> This is something similar to C++ soft from
> '90 where its in many cases used
> UB and work fine, but on new compilers it crashes because of that UB.
Again, UB and adversarial compilers is a separate topic from this, that infuriates me for entirely different reasons :D
> I had similar problems when I added more validations to user configs
> in my program.
> Before it accepted contradicting configs but somehow it still worked.
> Even enforcing some basic sanity in configs break many uses and make
> them unhappy
> as it forces them to update things that from their perspective "worked
> fine before".
Assuming this is not a security issue - which mismatching configs do - there are many options available for dealing with this kind of issue. Commercial OS vendors have to do this for actual compiled code doing the wrong thing, and have many ways to do so. Raymond Chen posted about some application that had a bunch of memory overruns that caused it to crash when a new allocator was introduced, and they fixed that by simply increasing the allocation size when that or similar applications performed suspicious allocations.
In your case there would be plenty of options, from starting with a warning, or using side channels to see if the config likely dates to the older release, to (scary/gross) writing a comment back into the config saying “there is a problem here”.
But also just to be very clear: an identifiable “this is questionable” issue in a config file, is not the same as a memory access violation, unless that config issue can cause your program to get into an invalid state, that can in turn lead to exploitable issues (i.e if your program was a setuid binary, and the config confusion would allow an unprivileged user to perform a privileged operation).
—Oliver
Received on 2025-08-31 22:12:13