dmd icon indicating copy to clipboard operation
dmd copied to clipboard

@safe code should do null check for members when appropriate

Open dlangBugzillaToGithub opened this issue 9 years ago • 4 comments

Steven Schveighoffer (@schveiguy) reported this on 2016-09-22T12:55:09Z

Transferred from https://issues.dlang.org/show_bug.cgi?id=16526

Description

If I have a struct like this:

struct S
{
  int[10_000] data
}

I can access memory I'm not supposed to, even in @safe code, that is beyond the zero page:

void main() @safe
{
   S * ptr;
   ptr.data[9999] = 5;
}

This data could be unmapped or mapped (on my 64-bit system, it's not mapped, so a segfault still occurs). But it is a possible way to have an exploit in @safe code.

I propose that if the compiler can detect at compile-time that access to a member of a struct is beyond the zero page if a pointer happens to point at null, then it should check for a null pointer (actually, it should just dereference the first word pointed at by the pointer, and trigger a segfault). It only needs to do this once if it can determine the pointer or reference will not change.

If the access to the member is determined at compile-time to point within one page of the front of the struct pointer (either by knowing at compile time the index being accessed, or knowing that the struct is less than one page in size), then no instrumentation is needed.

If the access cannot be determined to be within one page at compile-time, then the check should still be made during runtime.

If one takes a reference to a member that is outside the one-page limit, then the same check can be performed. There are still ways to access data beyond one page without incurring a check with this scheme, but we are talking about very weird and rare code.

This scheme should leave most code intact, and in rare cases, add a few null checks here and there. Note that by null check, you aren't actually checking against null, but simply using the built-in ability to segfault. So it's cheap.

dlangBugzillaToGithub avatar Sep 22 '16 12:09 dlangBugzillaToGithub

dfj1esp02 commented on 2016-09-22T16:01:17Z

struct S
{
	byte[3000] a,b;
}
void f(ref S s)
{
	g(s.b);
}
void g(ref byte[3000] b)
{
	b[2000]=0;
}

Similar with slicing:
void f(ref byte[6000] a)
{
	g(a[0..3000]);
}
void g(ref byte[3000] b)
{
	b[2000]=0;
}

dlangBugzillaToGithub avatar Sep 22 '16 16:09 dlangBugzillaToGithub

schveiguy (@schveiguy) commented on 2016-09-22T18:44:57Z

Yes, this is what I meant by "There are still ways to access data beyond one page without incurring a check with this scheme, but we are talking about very weird and rare code."

dlangBugzillaToGithub avatar Sep 22 '16 18:09 dlangBugzillaToGithub

schveiguy (@schveiguy) commented on 2016-09-22T18:48:02Z

(In reply to Sobirari Muhomori from comment #1)
> struct S
> {
> 	byte[3000] a,b;
> }
> void f(ref S s)
> {
> 	g(s.b);
> }

Actually, technically this could trigger a check of s address, since b's data is beyond the 1 page boundary.

dlangBugzillaToGithub avatar Sep 22 '16 18:09 dlangBugzillaToGithub

There's more discussion of this in https://github.com/dlang/dmd/issues/18315.

ntrel avatar May 04 '25 10:05 ntrel