Bug 19968 – @safe code can create invalid bools resulting in memory corruption
Status
RESOLVED
Resolution
FIXED
Severity
normal
Priority
P1
Component
dmd
Product
D
Version
D2
Platform
x86_64
OS
Linux
Creation time
2019-06-15T09:12:53Z
Last change time
2020-06-08T21:25:19Z
Keywords
pull, safe
Assigned to
No Owner
Creator
Tim
Comments
Comment #0 by tim.dlang — 2019-06-15T09:12:53Z
Variables of type bool are expected to be 0 or 1. Using void initialization this invariant can be wrong. This can result in memory corruption. See the following example:
import std.stdio;
static int[2] data;
static int[253] data2;
void test(bool b) @safe
{
data[b]++;
}
void main() @safe
{
bool b = void;
writeln(data, data2);
test(b);
writeln(data, data2);
}
It can happen, that data2 is modified here.
See http://forum.dlang.org/post/[email protected] for reference.
Another problem is, that the code generation for !b assumes b is 0 or 1. The following code shows, that bools can seem to be true and false at the same time:
import std.stdio;
void main() @safe
{
bool b = void;
if(b)
writeln("b seems to be true");
if(!b)
writeln("b seems to be false");
}
@trusted functions, that are correct for true and false may result in memory corruption for invalid values.
See http://forum.dlang.org/reply/[email protected] for reference.
Comment #1 by tim.dlang — 2019-06-15T09:19:37Z
The ABI specifies, that bool are 0 or 1: https://dlang.org/spec/abi.html#basic_types
There are multiple options to solve this bug:
1. Make sure @safe code can not create invalid bools.
2. Change the ABI, so all values are valid. This could result in slower code being generated.
Comment #2 by timon.gehr — 2019-06-15T13:26:23Z
Note that there is a general problem with `void` initialization, not only of bools but for all data types that have a (possibly implicit) invariant. If @safe code can `void`-initialize a struct with private members, @trusted code can't establish any invariants, which would cripple it.
Comment #3 by timon.gehr — 2019-06-15T13:29:31Z
Also, the spec currently states:
"Undefined Behavior: If a void initialized variable's value is used before it is set, the behavior is undefined."
https://dlang.org/spec/declaration.html#void_init
Unless this is changed (and I don't see a compelling reason to), there can't be _any_ `void` initialization in @safe code.
Comment #4 by ag0aep6g — 2019-06-15T13:39:53Z
(In reply to timon.gehr from comment #3)
> Also, the spec currently states:
>
> "Undefined Behavior: If a void initialized variable's value is used before
> it is set, the behavior is undefined."
>
> https://dlang.org/spec/declaration.html#void_init
>
> Unless this is changed (and I don't see a compelling reason to), there can't
> be _any_ `void` initialization in @safe code.
That's issue 18016, and Walter has an open PR to resolve it by changing the spec: <https://github.com/dlang/dlang.org/pull/2260>.
Comment #5 by tim.dlang — 2019-06-15T15:19:06Z
Maybe even types like ubyte can have the same problem on some architectures. If a parameter is stored in a register, which is bigger than the type, the compiler could assume the value is in the correct range. But an uninitialized variable could result in an uninitialized register.
Comment #6 by bugzilla — 2019-06-17T07:55:35Z
(In reply to Tim from comment #5)
> Maybe even types like ubyte can have the same problem on some architectures.
> If a parameter is stored in a register, which is bigger than the type, the
> compiler could assume the value is in the correct range. But an
> uninitialized variable could result in an uninitialized register.
When registers load a ubyte, and then the whole register is used, there must have been a conversion from ubyte to the larger size first, so I don't think this is an issue.
Comment #7 by bugzilla — 2019-06-17T07:59:16Z
> Variables of type bool are expected to be 0 or 1. Using void initialization this invariant can be wrong. This can result in memory corruption.
That is correct. But I don't think this generalizes to other integer types.
Comment #8 by bugzilla — 2019-06-17T08:03:44Z
(In reply to Walter Bright from comment #7)
> That is correct. But I don't think this generalizes to other integer types.
Mainly because the compiler doesn't do bounds checking when indexing by bool and the array size is known to be >= 2.
Comment #9 by dlang-bot — 2019-06-18T08:19:46Z
@WalterBright created dlang/dmd pull request #10055 "fix Issue 19968 - @safe code can create invalid bools resulting in me…" fixing this issue:
- fix Issue 19968 - @safe code can create invalid bools resulting in memory corruption
https://github.com/dlang/dmd/pull/10055
Comment #10 by tim.dlang — 2019-06-18T16:19:58Z
(In reply to Dlang Bot from comment #9)
> @WalterBright created dlang/dmd pull request #10055 "fix Issue 19968 - @safe
> code can create invalid bools resulting in me…" fixing this issue:
>
> - fix Issue 19968 - @safe code can create invalid bools resulting in memory
> corruption
>
> https://github.com/dlang/dmd/pull/10055
The pull request only fixes the specific example. Here is a new test case, that is still affected:
import std.stdio;
static int[5] data;
static int[251] data2;
void test(bool b) @safe
{
data[3 + b]++;
}
void main() @safe
{
bool b = void;
writeln(data, data2);
test(b);
writeln(data, data2);
}
In this case value range propagation determines, that the expression 3 + b is always in the range of indices for data. But since the type of 3 + b is not bool anymore, the pull request does not prevent the memory corruption.
In my opinion, it would be better to prevent creating invalid bools in @safe code.
Comment #11 by dlang-bot — 2019-07-22T08:56:15Z
dlang/dmd pull request #10055 "fix Issue 19968 - @safe code can create invalid bools resulting in me…" was merged into master:
- 523e7220017432a9a3db8379f6f827f742c8dff0 by Walter Bright:
fix Issue 19968 - @safe code can create invalid bools resulting in memory corruption
https://github.com/dlang/dmd/pull/10055
Comment #12 by ag0aep6g — 2019-08-20T22:22:57Z
(In reply to Tim from comment #0)
> import std.stdio;
> void main() @safe
> {
> bool b = void;
> if(b)
> writeln("b seems to be true");
> if(!b)
> writeln("b seems to be false");
> }
>
> @trusted functions, that are correct for true and false may result in memory
> corruption for invalid values.
I've filed a new issue for this: issue 20148.
Comment #13 by elronnd — 2020-01-06T03:05:28Z
I believe that void initialization of bools should result in their being zero-initialized. There is a deeper assumption that bools should be either 0 or 1; that shouldn't be broken by void initialization. Therefor it should be ensured that, whatever their value, it's either 0 or 1, and since it's as cheap to and them with 1 (to get a random result) as to and them with 0 (which is what mov target, 0 does anyway), zero-initialize them.
Comment #14 by qs.il.paperinik — 2020-06-08T21:25:19Z
(In reply to elronnd from comment #13)
> I believe that void initialization of bools should result in their being
> zero-initialized. [...] [S]ince it's as
> cheap to and them with 1 (to get a random result) as to and them with 0
> (which is what mov target, 0 does anyway), zero-initialize them.
It's cheap for a single bool, granted, but consider arrays of bools:
bool[large_num] array = void;
Void init is primarily a thing for static arrays. Silently initializing those with zeros is basically (performance) code breaking. Note that someone with writing that code with reasons did that for performance, so it *is* a breaking change.