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.