Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrongly trusted return statement in automem.vector.range() allows obtaining a reference to expired stack frame #26

Open
PetarKirov opened this issue Mar 16, 2019 · 7 comments

Comments

@PetarKirov
Copy link

PetarKirov commented Mar 16, 2019

This @trusted block of code:

// FIXME - why isn't &this @safe?
return Range(() @trusted { return &this; }(),
start,
end);

allows a pointer to the vector to escape:

/+ dub.sdl:
dependency "automem" version="==0.6.0"
dflags "-preview=dip1000" "-preview=dip25"
+/

import automem.vector;

@safe:

typeof(vector(1).range()) global;

void main()
{
    auto vec1 = vector(1, 2, 3);
    global = vec1.range; // compiles, but it shouldn't
}

Stack corruption PoC:

/+ dub.sdl:
dependency "automem" version="==0.6.0"
dflags "-preview=dip1000" "-preview=dip25"
+/

import std.stdio : writeln;
import automem.vector;

@safe:

typeof(vector(1).range()) global;

void main()
{
    escape;
    global.length.writeln; // 0
    stackSmash;
    global.length.writeln; // 42
}

void escape()
{
    auto vec1 = vector(1, 2, 3);
    global = vec1.range;
}

void stackSmash()
{
    long[4096] arr = 42;
}
$DC --version
DMD64 D Compiler v2.085.0
Copyright (C) 1999-2019 by The D Language Foundation, All Rights Reserved written by Walter Bright

dub automem_test.d
0
42
@PetarKirov PetarKirov changed the title Wrongly trusted return statement in automem.vector.range() allows a reference to expired stack frame Wrongly trusted return statement in automem.vector.range() allows obtaining a reference to expired stack frame Mar 16, 2019
@atilaneves
Copy link
Owner

Nice catch, taking a look. Thanks for reporting!

@atilaneves
Copy link
Owner

So: using @trusted obviously opens a can of worms. But: why isn't return scope enough for dmd to think it's safe to return a range with a pointer to this? It should track the lifetime of the returned range as being shorter than the vector that returned it (because of return scope) then disallow assigning it to a global. Am I missing something that would make this an automem versus a dmd bug if @trusted is removed?

@PetarKirov
Copy link
Author

PetarKirov commented Mar 18, 2019

I tried a couple of things with the current layout, but I couldn't figure out how to make it @safe.

So far, I'm thinking that only a layout like this could be easily made @safe:

struct Vector(T)
{
    Impl* impl;
    long start, end;

    struct Impl
    {
        long refs;
        long capacity;
        T[0] data;
    }
    auto range(long start, long end) scope return
    {
        return Vector!T(impl, start, end);
    }
}

(Disclaimer: untested, typing on the phone.)

@PetarKirov
Copy link
Author

PetarKirov commented Mar 19, 2019

It should track the lifetime of the returned range as being shorter than the vector that returned it (because of return scope) then disallow assigning it to a global.

Agreed, though at least dmd doesn't allow this code without the use of @trusted. I think this a good candidate for a bug report.

Am I missing something that would make this an automem versus a dmd bug if @trusted is removed?

It's a deficiency of dmd that it doesn't support @safe alternative for your use case.
Perhaps there's a way to make it work, I'll try to think more about that, without changing the object layout.

@atilaneves
Copy link
Owner

atilaneves commented Mar 19, 2019

Weirdly enough, this fails to compile as expected with dip1000:

typeof(Container.range()) global;

void main() @safe {
    auto c = Container();
    global = c.range;
}

struct Container {
    auto range() @safe return scope {
        static struct Range {
            Container *self;
        }

        return Range(&this);
    }
}
$ dmd -preview=dip1000 bug.d
bug.d(5): Error: address of variable c assigned to global with longer lifetime

@atilaneves
Copy link
Owner

Found out why: it's the _elements member variable. Add it to the example above and dmd goes "nope".

@atilaneves
Copy link
Owner

atilaneves added a commit that referenced this issue Mar 19, 2019
atilaneves added a commit that referenced this issue Mar 19, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants