Skip to content

Commit c8e59cb

Browse files
authored
Fix: prevent hanging state by forwarding protocol-level parsing errors into the message stream. (#388)
1 parent 03718c4 commit c8e59cb

File tree

5 files changed

+114
-3
lines changed

5 files changed

+114
-3
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 3.4.3
4+
5+
- Fix: prevent hanging state by forwarding protocol-level parsing errors into the message stream.
6+
37
## 3.4.2
48

59
- Fix: When a transaction is rolled back, do not expose the exception on rollback, rather the original exception from the transaction.

lib/src/messages/server_messages.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@ class DataRowMessage extends ServerMessage {
182182
values.add(Uint8List(0));
183183
} else if (dataSize == -1) {
184184
values.add(null);
185+
} else if (dataSize < -1) {
186+
throw AssertionError('Bad data size for field $i: $dataSize');
185187
} else {
186188
final rawBytes = reader.read(dataSize);
187189
values.add(rawBytes);

lib/src/v3/protocol.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,12 @@ StreamTransformer<Uint8List, ServerMessage> _readMessages(
7070
}
7171

7272
Future<void> handleChunk(Uint8List bytes) async {
73-
await framer.addBytes(bytes);
74-
emitFinishedMessages();
73+
try {
74+
await framer.addBytes(bytes);
75+
emitFinishedMessages();
76+
} catch (e, st) {
77+
listener.addErrorSync(e, st);
78+
}
7579
}
7680

7781
// Don't cancel this subscription on error! If the listener wants that,

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: postgres
22
description: PostgreSQL database driver. Supports statement reuse and binary protocol and connection pooling.
3-
version: 3.4.2
3+
version: 3.4.3
44
homepage: https://github.com/isoos/postgresql-dart
55
topics:
66
- sql
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import 'dart:async';
2+
import 'dart:io';
3+
4+
import 'package:postgres/postgres.dart';
5+
import 'package:test/test.dart';
6+
7+
import 'docker.dart';
8+
9+
void main() {
10+
withPostgresServer('unexpected protocol bytes', (server) {
11+
late Connection conn;
12+
late ServerSocket serverSocket;
13+
bool sendGarbageResponse = false;
14+
15+
setUp(() async {
16+
serverSocket = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0);
17+
serverSocket.listen((socket) async {
18+
final clientSocket = await Socket.connect(
19+
InternetAddress.loopbackIPv4, await server.port);
20+
late StreamSubscription socketSubs;
21+
late StreamSubscription clientSubs;
22+
socketSubs = socket.listen(clientSocket.add, onDone: () {
23+
socketSubs.cancel();
24+
clientSubs.cancel();
25+
clientSocket.close();
26+
}, onError: (e) {
27+
socketSubs.cancel();
28+
clientSubs.cancel();
29+
clientSocket.close();
30+
});
31+
final pattern = [68, 0, 0, 0, 11, 0, 1, 0, 0, 0, 1];
32+
clientSubs = clientSocket.listen((data) {
33+
if (sendGarbageResponse) {
34+
final i = _bytesIndexOf(data, pattern);
35+
if (i >= 0) {
36+
data[i + pattern.length - 4] = 255;
37+
data[i + pattern.length - 3] = 255;
38+
data[i + pattern.length - 2] = 255;
39+
data[i + pattern.length - 1] = 250;
40+
}
41+
socket.add(data);
42+
} else {
43+
socket.add(data);
44+
}
45+
}, onDone: () {
46+
socketSubs.cancel();
47+
clientSubs.cancel();
48+
clientSocket.close();
49+
}, onError: (e) {
50+
socketSubs.cancel();
51+
clientSubs.cancel();
52+
clientSocket.close();
53+
});
54+
});
55+
56+
final endpoint = await server.endpoint();
57+
conn = await Connection.open(
58+
Endpoint(
59+
host: endpoint.host,
60+
port: serverSocket.port,
61+
database: endpoint.database,
62+
password: endpoint.password,
63+
username: endpoint.username,
64+
),
65+
settings: ConnectionSettings(
66+
sslMode: SslMode.disable,
67+
),
68+
);
69+
});
70+
71+
tearDown(() async {
72+
await conn.close();
73+
await serverSocket.close();
74+
});
75+
76+
test('inject bad bytes', () async {
77+
await conn.execute('SELECT 1;');
78+
sendGarbageResponse = true;
79+
await expectLater(
80+
() => conn.execute('SELECT 2;', queryMode: QueryMode.simple),
81+
throwsA(isA<PgException>()));
82+
expect(conn.isOpen, false);
83+
});
84+
});
85+
}
86+
87+
int _bytesIndexOf(List<int> data, List<int> pattern) {
88+
for (var i = 0; i < data.length - pattern.length; i++) {
89+
var matches = true;
90+
for (var j = 0; j < pattern.length; j++) {
91+
if (data[i + j] != pattern[j]) {
92+
matches = false;
93+
break;
94+
}
95+
}
96+
if (matches) {
97+
return i;
98+
}
99+
}
100+
return -1;
101+
}

0 commit comments

Comments
 (0)