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

Fix issue 14137: std.socket.getAddressInfo breaks @safe #4009

Merged
merged 1 commit into from
Apr 18, 2016

Conversation

quickfur
Copy link
Member

Remove abuse of @trusted in template function getAddressInfo that cannot guarantee that the incoming type argument T is actually @safe. Localize @trusted to the single call to getAddressInfoImpl, so that any @system code in T will be caught by the type system.

Add unittest to ensure such examples of T will be rejected at compile-time.

Mark normal unittest for getAddressInfo as @safe to ensure that the function body itself does not introduce any non-@safe code.

The rest of this module has frightening amounts of @trusted functions, which need to be properly vetted (and probably the scope of @trusted reduced), but let's leave that for another battle.

Remove abuse of @trusted in template function getAddressInfo that cannot
guarantee that the incoming type argument is @safe. Localize @trusted
block of the function to the single call to getAddressInfoImpl(), so
that any @System code in T will be caught by the type system.

Add unittest to ensure such examples of T will be rejected at
compile-time.

Mark normal unittest for getAddressInfo as @safe to ensure that the
function body itself does not introduce any non-@safe code.
@dlang-bot
Copy link
Contributor

Fix Bugzilla Description
14137 std.socket.getAddressInfo breaks @safety

@JakobOvrum
Copy link
Member

An alternative would be to check isArray!(typeof(option)) && is(typeof(option) : const(char)[]), but I guess that would be a gratuitous breaking change.


I've been thinking about what to do with Socket.receive:

auto buffer = new Object[](1);
socket.receive(buffer);

It clearly doesn't have a memory safe interface. We could of course start thinking about type-aware wrappers, but considering the ubiquity of void[] for I/O, I thought it might be a good idea to instead disallow T[] -> void[] in @safe code when hasIndirections!T. Conversion to const(void)[] and immutable(void)[] might still be supported.


Anyway, that's for another time. LGTM.

@quickfur
Copy link
Member Author

Well, technically the problem isn't with T[] -> void[] conversion in @safe code, because after that conversion you can't really do much with the void[] as long as you remain in @safe code. IMO the real issue is that functions that take void[] should not be marked @trusted (unless it's const(void[]), i.e., read-only, of course), because you're effectively claiming that the function has a memory-safe interface even though it takes in an array of arbitrary type, including types with indirection, and may do things with it that are completely unsafe.

@quickfur
Copy link
Member Author

Raised a new issue here: https://issues.dlang.org/show_bug.cgi?id=15702

@JakobOvrum
Copy link
Member

My point is that if conversion from T[] -> void[] was disallowed in @safe for hasIndirections!T, functions taking void[] would be safely callable from @safe, no matter what they cast to internally. This makes void[] (mutable) usable in @trusted interfaces.

Note that @safe functions don't make any memory safety guarantees when called from @system functions:

void foo(Object o) @safe {}

void main() @system {
    size_t i = void;
    foo(cast(Object)cast(void*)i);
}

@yebblies
Copy link
Member

Conversion of T[] to void[] is not @safe.

int*[] x = [new int(0), new int(1), new int(2)];
size_t[] y = [0x12345678, 0x12345678, 0x12345678];
void[] v = x[];
v[] = y[];

And the same thing happens if you replace void[] with ubyte[].

So it looks like we need to make T[] -> *[] unsafe in general for any T with indirections. T[] -> const(*)[] should still be fine.

@quickfur
Copy link
Member Author

What about copying void[] to void[]? Even though the current rules say that this is safe (copying arrays of the same type from one to another), it's actually not safe, because void[] is a type-erased array, and we don't know what its original type was. Allowing copying from void[] to void[] seems to be recipe for disaster, since we can't even check at runtime whether the destination array has the same type as the source.

Having said that, though, if @safe code cannot construct a void[] out of an array with indirections in the first place, perhaps this point will be moot.

@yebblies
Copy link
Member

It's true, we could and maybe should tighten up void[] assignment as well, and then we could keep the T[] to void[] conversion.

@jmdavis
Copy link
Member

jmdavis commented Feb 19, 2016

Conversion of T[] to void[] is not @safe.

    int*[] x = [new int(0), new int(1), new int(2)];
    size_t[] y = [0x12345678, 0x12345678, 0x12345678];
    void[] v = x[];
    v[] = y[];

But the problem there is that you're mutating v. Wouldn't

    int*[] x = [new int(0), new int(1), new int(2)];
    void[] v = x[];

be perfectly @safe? I fail to see how simply converting an array to void[] is a problem. It's once you start mucking around with that void[] that things go south - which would include copying an array on top of the void[] like in your example.

@JakobOvrum
Copy link
Member

Yeah, writing to void[] must be disallowed in @safe. My proposal is specifically to enable void[] in memory safe interfaces, allowing us to integrate the void[]-for-I/O idiom with @safe.

@jmdavis
Copy link
Member

jmdavis commented Feb 19, 2016

Ah. I responded too quickly and missed this comment:

It's true, we could and maybe should tighten up void[] assignment as well, and then we could keep the T[] to void[] conversion.

Yes. Conversion to void[] should be fine, but anything which interprets what the void[] contains can't be @safe, because whether it's @safe or not depends on what it contains. Reassigning the void[] should be fine, but the vectorized assignment definitely would not be.

@jmdavis
Copy link
Member

jmdavis commented Feb 19, 2016

Yeah, writing to void[] must be disallowed in @safe. My proposal is specifically to enable void[] in memory safe interfaces, allowing us to integrate the void[]-for-I/O idiom with @safe.

The question is whether it's @safe to convert void[] to const(ubyte)[]. I would have thought that it would be, since it's just treating it as bytes, and what the data actually means shouldn't matter - in which case, having a socket function which accepted void[] for writing to the socket (in which case, presumably it could be const(void[])) should be fine, but doing something like receiving data on a socket and writing it to void[] definitely wouldn't be. But there could be something subtle I'm missing that makes it so that converting void[] to const(ubyte)[] would be a problem.

@JakobOvrum
Copy link
Member

I'll say it a third time. Receiving data on a socket and writing it to void[] would be @safe if T[] -> void[] was disallowed for hasIndirections!T. Socket.receive would remain @trusted as recv doesn't have a memory safe interface even with the amended rule.


As a completely separate issue, T[] -> const(void)[] and immutable(T)[] -> immutable(void)[] should be allowed in @safe unless someone can demonstrate how this could be used to corrupt memory.

@jmdavis
Copy link
Member

jmdavis commented Feb 19, 2016

I'll say it a third time. Receiving data on a socket and writing it to void[] would be @safe if T[] -> void[] was disallowed for hasIndirections!T. Socket.receive would remain @trusted as recv doesn't have a memory safe interface even with the amended rule.

Well, since the conversion to void[] would have been done elsewhere, it's not like Socket.receive can know that the data it's operating on is safe to mutate. So, we either have to always treat writing to void[] as @system, or we have to treat converting any T[] for which hasIndirections!T is true as @system.

But the simple fact that Socket.receive is calling C functions is going to force it to be @trusted no matter what we do with void[] (that, and the fact that it has to use the ptr member on the void[] to pass it to the C function).

@JakobOvrum
Copy link
Member

Well, since the conversion to void[] would have been done elsewhere, it's not like Socket.receive can know that the data it's operating on is safe to mutate.

It's always safe to mutate with the amended rule.

So, we either have to always treat writing to void[] as @system, or we have to treat converting any T[] for which hasIndirections!T is true as @system.

I suggest we do both, although with the latter instated, the former becomes just a safeguard (casting before mutating is more visible than no casting required), not a mechanic of supporting @safe.

But the simple fact that Socket.receive is calling C functions is going to force it to be @trusted no matter what we do with void[] (that, and the fact that it has to use the ptr member on the void[] to pass it to the C function).

Whether a function is a C function or D function is entirely orthogonal to @safe.

@jmdavis
Copy link
Member

jmdavis commented Feb 19, 2016

Whether a function is a C function or D function is entirely orthogonal to @safe.

Well, in theory, but to mark a C function with @safe, you'd have to actually know its implementation. They're effectively @system given that the compiler can't verify them, so it's up to the programmer to determine whether what they're doing is @trust-able or not. But they're @system by default regardless, and as it stands, none of the socket functions in druntime are marked as @trusted. So, that combined with the fact that they tend to take require that you use the ptr from an array means that any function using them is going to be @system regardless of what's going on with void[].

@@ -971,7 +971,23 @@ AddressInfo[] getAddressInfo(T...)(in char[] node, T options) @trusted
static assert(0, "Unknown getAddressInfo option type: " ~ typeof(option).stringof);
}

return getAddressInfoImpl(node, service, &hints);
return () @trusted { return getAddressInfoImpl(node, service, &hints); }();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getAddressInfoImpl looks like it has a memory safe interface. Memory safety attributes should be handled there instead.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you're saying it should be getAddressInfoImpl that gets marked @trusted?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind, the hints parameter has possible unsafe configurations, such as invalid ai_addrlen.

@JakobOvrum
Copy link
Member

Well, in theory, but to mark a C function with @safe, you'd have to actually know its implementation. They're effectively @system given that the compiler can't verify them, so it's up to the programmer to determine whether what they're doing is @Trust-Able or not.

C functions can have available body when implemented in D, and D functions can also have unavailable body. Whether a function is a C function or not is a red herring.

But they're @system by default regardless, and as it stands, none of the socket functions in druntime are marked as @trusted.

D functions are also @system by default. Socket functions in druntime's bindings with memory safe interfaces can and should be marked @trusted. You don't need to know the implementation to make this decision - just the interface, which of course includes documentation of behaviour.

So, that combined with the fact that they tend to take require that you use the ptr from an array means that any function using them is going to be @system regardless of what's going on with void[].

The slice ptr property is allowed in @safe and that's not a mistake. The yielded pointer is either null or points to a valid memory address containing an initialized object of infinite lifetime. The fixed-length array ptr property is currently also accepted in @safe, but this is a mistake.

The reason that recv's interface is not memory safe is because it treats the pointer parameter as an array where the length is passed separately.

@jmdavis
Copy link
Member

jmdavis commented Feb 19, 2016

Hmmm. I thought that ptr was @system, but that's probably because it usually gets used either in conjunction with pointer arithmetic or passing it to something that takes the length as a separate argument. But you're right, if all you're doing is looking at the first element in the array, it would actually be @safe.

@dnadlinger
Copy link
Member

The slice ptr property is allowed in @safe and that's not a mistake.

Are you sure about that?

int main() @safe {
    auto a = [0, 1, 2];
    auto b = a[$..$];
    return *b.ptr;
}

@JakobOvrum
Copy link
Member

@klickverbot, no, I'm not. I guess your example would deref a[3], one element beyond the bounds of a? There are a number of ways we can deal with that, and I'm not sure which is the best one.

@dnadlinger
Copy link
Member

@JakobOvrum: Yep, it accesses the beyond-the-last pointer. I knew reported this somewhere already: https://issues.dlang.org/show_bug.cgi?id=11176

@JakobOvrum
Copy link
Member

@klickverbot, definitely something we need to fix.

It doesn't have any bearing on the use of recv though, which needs to be manually vetted for memory safety regardless.

@dnadlinger
Copy link
Member

@JakobOvrum: Agreed, it is unrelated to recv.

@DmitryOlshansky
Copy link
Member

Tons of unrelated discussion. The pull looks good to me

@DmitryOlshansky
Copy link
Member

Auto-merge toggled on

@DmitryOlshansky DmitryOlshansky merged commit 8a34531 into dlang:master Apr 18, 2016
@quickfur quickfur deleted the issue14137 branch March 13, 2017 22:43
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

Successfully merging this pull request may close these issues.

7 participants