Skip to content

Commit 9bf1f25

Browse files
mralephCommit Queue
authored andcommitted
[vm/io] Fix crashes in Socket.setOption
Don't call native methods on a socket which is closed or in process of being closed. On some OSes this will crash the VM because underlying FD is actually a malloc allocated handle object which will be destroyed by the event handler once it receives and processes close request. BUG=b/335437875 TEST=standalone/io/abrupt_close_test Change-Id: I557d725a6cce020a6a1e3df0bd3e2c836a6a6964 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/467841 Reviewed-by: Martin Kustermann <[email protected]> Commit-Queue: Slava Egorov <[email protected]>
1 parent a22f4d3 commit 9bf1f25

File tree

2 files changed

+86
-3
lines changed

2 files changed

+86
-3
lines changed

sdk/lib/_internal/vm/bin/socket_patch.dart

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1593,7 +1593,7 @@ base class _NativeSocket extends _NativeSocketNativeWrapper
15931593
int get port {
15941594
if (localAddress.type == InternetAddressType.unix) return 0;
15951595
if (localPort != 0) return localPort;
1596-
if (isClosing || isClosed) throw const SocketException.closed();
1596+
_ensureNotClosingOrClosed();
15971597
var result = _nativeGetPort();
15981598
if (result is OSError) {
15991599
throw result;
@@ -1603,14 +1603,14 @@ base class _NativeSocket extends _NativeSocketNativeWrapper
16031603

16041604
int get remotePort {
16051605
if (localAddress.type == InternetAddressType.unix) return 0;
1606-
if (isClosing || isClosed) throw const SocketException.closed();
1606+
_ensureNotClosingOrClosed();
16071607
return _nativeGetRemotePeer()[1];
16081608
}
16091609

16101610
InternetAddress get address => localAddress;
16111611

16121612
InternetAddress get remoteAddress {
1613-
if (isClosing || isClosed) throw const SocketException.closed();
1613+
_ensureNotClosingOrClosed();
16141614
var result = _nativeGetRemotePeer();
16151615
var addr = result[0] as List<Object?>;
16161616
var type = InternetAddressType._from(addr[0] as int);
@@ -1951,6 +1951,8 @@ base class _NativeSocket extends _NativeSocketNativeWrapper
19511951
}
19521952

19531953
dynamic getOption(SocketOption option) {
1954+
_ensureNotClosingOrClosed();
1955+
19541956
// TODO(40614): Remove once non-nullability is sound.
19551957
ArgumentError.checkNotNull(option, "option");
19561958
var result = _nativeGetOption(option._value, address.type._value);
@@ -1959,13 +1961,17 @@ base class _NativeSocket extends _NativeSocketNativeWrapper
19591961
}
19601962

19611963
bool setOption(SocketOption option, value) {
1964+
_ensureNotClosingOrClosed();
1965+
19621966
// TODO(40614): Remove once non-nullability is sound.
19631967
ArgumentError.checkNotNull(option, "option");
19641968
_nativeSetOption(option._value, address.type._value, value);
19651969
return true;
19661970
}
19671971

19681972
Uint8List getRawOption(RawSocketOption option) {
1973+
_ensureNotClosingOrClosed();
1974+
19691975
// TODO(40614): Remove once non-nullability is sound.
19701976
ArgumentError.checkNotNull(option, "option");
19711977
ArgumentError.checkNotNull(option.value, "option.value");
@@ -1974,6 +1980,8 @@ base class _NativeSocket extends _NativeSocketNativeWrapper
19741980
}
19751981

19761982
void setRawOption(RawSocketOption option) {
1983+
_ensureNotClosingOrClosed();
1984+
19771985
// TODO(40614): Remove once non-nullability is sound.
19781986
ArgumentError.checkNotNull(option, "option");
19791987
ArgumentError.checkNotNull(option.value, "option.value");
@@ -2009,6 +2017,8 @@ base class _NativeSocket extends _NativeSocketNativeWrapper
20092017
}
20102018

20112019
void joinMulticast(InternetAddress addr, NetworkInterface? interface) {
2020+
_ensureNotClosingOrClosed();
2021+
20122022
final interfaceAddr =
20132023
multicastAddress(addr, interface) as _InternetAddress?;
20142024
var interfaceIndex = interface == null ? 0 : interface.index;
@@ -2020,6 +2030,8 @@ base class _NativeSocket extends _NativeSocketNativeWrapper
20202030
}
20212031

20222032
void leaveMulticast(InternetAddress addr, NetworkInterface? interface) {
2033+
_ensureNotClosingOrClosed();
2034+
20232035
final interfaceAddr =
20242036
multicastAddress(addr, interface) as _InternetAddress?;
20252037
var interfaceIndex = interface == null ? 0 : interface.index;
@@ -2034,6 +2046,12 @@ base class _NativeSocket extends _NativeSocketNativeWrapper
20342046
return Platform.isWindows && _nativeHasPendingWrite();
20352047
}
20362048

2049+
// Native methods are not guarding against closed sockets, which can
2050+
// lead to crashes.
2051+
void _ensureNotClosingOrClosed() {
2052+
if (isClosing || isClosed) throw const SocketException.closed();
2053+
}
2054+
20372055
@pragma("vm:external-name", "Socket_SetSocketId")
20382056
external void _nativeSetSocketId(int id, int typeFlags);
20392057
@pragma("vm:external-name", "Socket_Available")
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// Check that Socket.setOption does not crash when socket closes due to
6+
// an error.
7+
8+
import 'dart:async';
9+
import 'dart:io';
10+
11+
import 'package:expect/async_helper.dart';
12+
import 'package:expect/expect.dart';
13+
14+
void main(List<String> args) async {
15+
var clientDone = false;
16+
var serverDone = false;
17+
18+
asyncStart();
19+
// Start the server which will accept a connection and then immediately
20+
// shutdown itself and the incoming connection.
21+
final server = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0);
22+
late final StreamSubscription serverSub;
23+
serverSub = server.listen((socket) async {
24+
socket.destroy();
25+
serverSub.cancel();
26+
server.close();
27+
serverDone = true;
28+
});
29+
30+
// Connect to the server and trigger a socket error by writing into a closed
31+
// socket (because server shuts down immediately after accepting the
32+
// connection).
33+
final socket = await Socket.connect(
34+
InternetAddress.loopbackIPv4,
35+
server.port,
36+
);
37+
final clientSub = socket.listen(
38+
(_) {},
39+
onDone: () {
40+
clientDone = true;
41+
},
42+
onError: (e) {},
43+
);
44+
socket.add([0]);
45+
socket.flush().ignore(); // Ignore write error, otherwise it will be uncaught.
46+
47+
// Now repeat
48+
await Future.delayed(Duration(milliseconds: 10));
49+
for (var i = 0; i < 1000; i++) {
50+
try {
51+
socket.setOption(SocketOption.tcpNoDelay, true);
52+
} on SocketException catch (e) {
53+
// We expect SocketException.closed() but no other error.
54+
if (e.message != SocketException.closed().message) {
55+
rethrow;
56+
}
57+
}
58+
59+
await Future.delayed(Duration(milliseconds: 1));
60+
}
61+
Expect.isTrue(clientDone);
62+
Expect.isTrue(serverDone);
63+
socket.destroy();
64+
asyncEnd();
65+
}

0 commit comments

Comments
 (0)