Skip to content

Commit 5dc586f

Browse files
authored
Merge pull request #5661 from sysown/fix/3.0.8-review-items
Fix GTID range validation for 3.0.8 review
2 parents 7ca0d15 + ae88c07 commit 5dc586f

7 files changed

Lines changed: 174 additions & 30 deletions

File tree

include/PgSQL_Session.h

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -394,8 +394,9 @@ class PgSQL_Session : public Base_Session<PgSQL_Session, PgSQL_Data_Stream, PgSQ
394394
// admin var off, result transfer already started, or a preflight failed).
395395
bool handler_minus1_PoisonTransaction(PgSQL_Data_Stream* myds);
396396
// While tx_poisoned, classify a 'Q' packet and either clear the poison
397-
// and synthesize a ROLLBACK response (for ROLLBACK / COMMIT / ABORT /
398-
// ROLLBACK TO SAVEPOINT) or reject with ERROR 25P02 (anything else).
397+
// and synthesize a ROLLBACK response (for plain whole-transaction
398+
// ROLLBACK / COMMIT / ABORT / END) or reject with ERROR 25P02
399+
// (anything else, including ROLLBACK TO SAVEPOINT).
399400
// Returns true if the packet was handled here. Increments the
400401
// pgsql_tx_poisoned_{recovered,rejected_statements}_total counters.
401402
bool handler_poisoned_simple_query(PtrSize_t* pkt);
@@ -481,12 +482,12 @@ class PgSQL_Session : public Base_Session<PgSQL_Session, PgSQL_Data_Stream, PgSQ
481482
// the client, destroy the backend pool connection, and set this flag
482483
// true instead of tearing down the client session. While this is true,
483484
// the query intake path short-circuits before query rules:
484-
// ROLLBACK / ROLLBACK TO SAVEPOINT / ABORT -> synthesize
485+
// plain ROLLBACK / ABORT -> synthesize
485486
// CommandComplete('ROLLBACK') + ReadyForQuery('I'), clear flag.
486-
// COMMIT -> same ROLLBACK response + NoticeResponse carrying the
487+
// plain COMMIT / END -> same ROLLBACK response + NoticeResponse carrying the
487488
// "there is no transaction in progress" warning, clear flag.
488-
// anything else (including RELEASE SAVEPOINT) -> reply ERROR 25P02
489-
// + ReadyForQuery('E'), stay poisoned.
489+
// anything else (including ROLLBACK TO SAVEPOINT and RELEASE SAVEPOINT)
490+
// -> reply ERROR 25P02 + ReadyForQuery('E'), stay poisoned.
490491
bool tx_poisoned{ false };
491492

492493
#ifdef DEBUG

include/proxysql_gtid.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class TrxId_Interval {
1919
explicit TrxId_Interval(const trxid_t trxid);
2020
explicit TrxId_Interval(const char* s);
2121
explicit TrxId_Interval(const std::string& s);
22+
static bool parse(const char* s, TrxId_Interval* out);
2223

2324
const bool contains(const TrxId_Interval& other);
2425
const bool contains(trxid_t trxid);
@@ -53,4 +54,4 @@ class GTID_Set {
5354
const std::string to_string(void);
5455
};
5556

56-
#endif /* PROXYSQL_GTID */
57+
#endif /* PROXYSQL_GTID */

lib/GTID_Server_Data.cpp

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@ bool GTID_Server_Data::read_next_gtid() {
330330
char rec_msg[80];
331331
if (strncmp(data+pos,(char *)"ST=",3)==0) {
332332
// we are reading the bootstrap
333+
bool invalid_msg = false;
333334
char *bs = (char *)malloc(l+1-3); // length + 1 (null byte) - 3 (header)
334335
memcpy(bs, data+pos+3, l-3);
335336
bs[l-3] = '\0';
@@ -361,14 +362,31 @@ bool GTID_Server_Data::read_next_gtid() {
361362
p++;
362363
}
363364
}
365+
*p = '\0';
364366
} else { // we are reading the trxid or trxid range
365-
updated = gtid_executed.add((std::string)uuid_server, subtoken) || updated;
367+
TrxId_Interval iv(trxid_t(0));
368+
if (!TrxId_Interval::parse(subtoken, &iv)) {
369+
invalid_msg = true;
370+
break;
371+
}
372+
updated = gtid_executed.add((std::string)uuid_server, iv) || updated;
366373
}
367374
}
375+
if (invalid_msg || j == 0 || j%2 != 0) {
376+
invalid_msg = true;
377+
break;
378+
}
368379
}
369380
pos += l+1;
370381
free(bs);
371382

383+
if (invalid_msg) {
384+
proxy_warning("GTID: invalid bootstrap message from binlog reader on port %d for server %s:%d, disconnecting\n",
385+
port, address, mysql_port);
386+
active = false;
387+
return false;
388+
}
389+
372390
if (updated) {
373391
events_read++;
374392
}
@@ -406,19 +424,33 @@ bool GTID_Server_Data::read_next_gtid() {
406424
ul = a-rec_msg-3;
407425
strncpy(uuid_server,rec_msg+3,ul);
408426
uuid_server[ul] = 0;
409-
gtid_executed.add((std::string)uuid_server, a+1);
427+
{
428+
TrxId_Interval iv(trxid_t(0));
429+
if (!TrxId_Interval::parse(a+1, &iv)) {
430+
invalid_msg = true;
431+
break;
432+
}
433+
gtid_executed.add((std::string)uuid_server, iv);
434+
}
410435
events_read++;
411436
break;
412437
case '4': // trxid range, reuse last UUID
413-
gtid_executed.add((std::string)uuid_server, rec_msg+3);
438+
{
439+
TrxId_Interval iv(trxid_t(0));
440+
if (!TrxId_Interval::parse(rec_msg+3, &iv)) {
441+
invalid_msg = true;
442+
break;
443+
}
444+
gtid_executed.add((std::string)uuid_server, iv);
445+
}
414446
events_read++;
415447
break;
416448
default:
417449
invalid_msg = true;
418450
}
419451

420452
if (invalid_msg) {
421-
proxy_warning("GTID: unsupported message (%s) from binlog reader on port %d for server %s:%d, disconnecting\n",
453+
proxy_warning("GTID: invalid or unsupported message (%s) from binlog reader on port %d for server %s:%d, disconnecting\n",
422454
rec_msg, port, address, mysql_port);
423455
active = false;
424456
return false;
@@ -450,4 +482,3 @@ void * GTID_syncer_run() {
450482
//sleep(1000);
451483
return NULL;
452484
}
453-

lib/PgSQL_Session.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -458,11 +458,11 @@ PgSQL_Session::~PgSQL_Session() {
458458
// message.
459459
//
460460
// Response shape:
461-
// * Recovery (ROLLBACK / ROLLBACK TO SAVEPOINT / ABORT / COMMIT):
461+
// * Recovery (plain ROLLBACK / ABORT / COMMIT / END):
462462
// CommandComplete('ROLLBACK') + ReadyForQuery('I'). For COMMIT also a
463463
// preceding NoticeResponse with "there is no transaction in progress"
464464
// — matches Postgres native behavior for COMMIT inside an aborted tx.
465-
// * Rejection (anything else, incl. RELEASE SAVEPOINT):
465+
// * Rejection (anything else, incl. ROLLBACK TO SAVEPOINT / RELEASE SAVEPOINT):
466466
// ErrorResponse(25P02) + ReadyForQuery('E'), tx_poisoned stays true.
467467
bool PgSQL_Session::handler_poisoned_simple_query(PtrSize_t* pkt) {
468468
if (pkt->size <= 5) {

lib/proxysql_gtid.cpp

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#include <cerrno>
2+
#include <cctype>
13
#include <cstdio>
24
#include <cstdlib>
35
#include <string>
@@ -18,23 +20,61 @@ TrxId_Interval::TrxId_Interval(const trxid_t _start, const trxid_t _end) {
1820
TrxId_Interval::TrxId_Interval(const trxid_t trxid) : TrxId_Interval(trxid, trxid) {
1921
}
2022

23+
static bool parse_trxid_component(const char*& p, trxid_t& out) {
24+
if (p == nullptr || !std::isdigit(static_cast<unsigned char>(*p))) {
25+
return false;
26+
}
27+
28+
errno = 0;
29+
char* end = nullptr;
30+
long long parsed = strtoll(p, &end, 10);
31+
if (end == p || errno == ERANGE || parsed < 0) {
32+
return false;
33+
}
34+
35+
out = static_cast<trxid_t>(parsed);
36+
p = end;
37+
return true;
38+
}
39+
40+
bool TrxId_Interval::parse(const char* s, TrxId_Interval* out) {
41+
if (s == nullptr || out == nullptr) {
42+
return false;
43+
}
44+
45+
const char* p = s;
46+
trxid_t _start = 0;
47+
trxid_t _end = 0;
48+
49+
if (!parse_trxid_component(p, _start)) {
50+
return false;
51+
}
52+
53+
_end = _start;
54+
if (*p == '-') {
55+
p++;
56+
if (!parse_trxid_component(p, _end)) {
57+
return false;
58+
}
59+
}
60+
61+
if (*p != '\0') {
62+
return false;
63+
}
64+
65+
*out = TrxId_Interval(_start, _end);
66+
return true;
67+
}
68+
2169
// Initializes a trxid interval from a C string buffer, in [trxid]{-[trxid]} format.
2270
TrxId_Interval::TrxId_Interval(const char *s) {
2371
start = 0;
2472
end = 0;
2573

26-
if (s == nullptr) {
27-
return;
28-
}
29-
30-
trxid_t _start = 0, _end = 0;
31-
32-
if (sscanf(s, "%ld-%ld", &_start, &_end) == 2) {
33-
start = _start;
34-
end = _end;
35-
} else if (sscanf(s, "%ld", &_start) == 1) {
36-
start = _start;
37-
end = _start;
74+
TrxId_Interval iv(trxid_t(0));
75+
if (parse(s, &iv)) {
76+
start = iv.start;
77+
end = iv.end;
3878
}
3979

4080
if (start > end) {
@@ -208,7 +248,11 @@ bool GTID_Set::add(const std::string& uuid, const trxid_t& start, const trxid_t&
208248

209249
// Adds a new trxid range for a given UUID, as a C string buffer. Returns true if the set was modified, false otherwise.
210250
bool GTID_Set::add(const std::string& uuid, const char *s) {
211-
return add(uuid, TrxId_Interval(s));
251+
TrxId_Interval iv(trxid_t(0));
252+
if (!TrxId_Interval::parse(s, &iv)) {
253+
return false;
254+
}
255+
return add(uuid, iv);
212256
}
213257

214258
// Adds a new trxid range for a given UUID, as a string. Returns true if the set was modified, false otherwise.
@@ -252,4 +296,4 @@ const std::string GTID_Set::to_string(void) {
252296
}
253297

254298
return out.str();
255-
}
299+
}

test/tap/tests/unit/gtid_server_data_unit-t.cpp

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,56 @@ static void test_unknown_message_disconnects() {
193193
ok(sd.events_read == 1, "unknown: events_read NOT incremented");
194194
}
195195

196+
/**
197+
* @brief Malformed bootstrap trxid ranges disconnect without counting an event.
198+
*/
199+
static void test_malformed_bootstrap_disconnects() {
200+
GTID_Server_Data sd(nullptr, (char *)"127.0.0.1", 0, 3306);
201+
202+
std::string msg = std::string("ST=") + UUID_A + ":abc\n";
203+
stuff_buffer(sd, msg);
204+
205+
ok(sd.read_next_gtid() == false, "malformed ST: returns false");
206+
ok(sd.active == false, "malformed ST: active set to false");
207+
ok(sd.events_read == 0, "malformed ST: events_read NOT incremented");
208+
ok(sd.gtid_exists((char *)UUID_A_STRIPPED, 0) == false, "malformed ST: trxid 0 was not added");
209+
}
210+
211+
/**
212+
* @brief Malformed I3 trxid ranges disconnect without counting an event.
213+
*/
214+
static void test_malformed_i3_disconnects() {
215+
GTID_Server_Data sd(nullptr, (char *)"127.0.0.1", 0, 3306);
216+
217+
std::string msg = std::string("I3=") + UUID_A_STRIPPED + ":10-abc\n";
218+
stuff_buffer(sd, msg);
219+
220+
ok(sd.read_next_gtid() == false, "malformed I3: returns false");
221+
ok(sd.active == false, "malformed I3: active set to false");
222+
ok(sd.events_read == 0, "malformed I3: events_read NOT incremented");
223+
ok(sd.gtid_exists((char *)UUID_A_STRIPPED, 0) == false, "malformed I3: trxid 0 was not added");
224+
ok(sd.gtid_exists((char *)UUID_A_STRIPPED, 10) == false, "malformed I3: trxid 10 was not added");
225+
}
226+
227+
/**
228+
* @brief Malformed I4 trxid ranges disconnect and preserve earlier event count.
229+
*/
230+
static void test_malformed_i4_disconnects() {
231+
GTID_Server_Data sd(nullptr, (char *)"127.0.0.1", 0, 3306);
232+
233+
std::string msg1 = std::string("I3=") + UUID_B_STRIPPED + ":10-20\n";
234+
stuff_buffer(sd, msg1);
235+
sd.read_next_gtid();
236+
237+
std::string msg2 = "I4=30-40x\n";
238+
stuff_buffer(sd, msg2);
239+
240+
ok(sd.read_next_gtid() == false, "malformed I4: returns false");
241+
ok(sd.active == false, "malformed I4: active set to false");
242+
ok(sd.events_read == 1, "malformed I4: events_read NOT incremented");
243+
ok(sd.gtid_exists((char *)UUID_B_STRIPPED, 30) == false, "malformed I4: trxid 30 was not added");
244+
}
245+
196246
/**
197247
* @brief Multiple messages in sequence: ST bootstrap, then I1, I3, I2, I4.
198248
*/
@@ -287,7 +337,7 @@ static void test_incomplete_message() {
287337
}
288338

289339
int main() {
290-
plan(70);
340+
plan(83);
291341

292342
test_bootstrap_single(); // 6 assertions
293343
test_bootstrap_range(); // 8 assertions
@@ -297,6 +347,9 @@ int main() {
297347
test_i3_range(); // 8 assertions
298348
test_i4_range_reuse_uuid(); // 7 assertions
299349
test_unknown_message_disconnects(); // 5 assertions
350+
test_malformed_bootstrap_disconnects(); // 4 assertions
351+
test_malformed_i3_disconnects(); // 5 assertions
352+
test_malformed_i4_disconnects(); // 4 assertions
300353
test_mixed_sequence(); // 14 assertions
301354
test_read_all_stops_on_unknown(); // 4 assertions
302355
test_empty_buffer(); // 3 assertions

test/tap/tests/unit/gtid_set_unit-t.cpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,19 @@ static void test_add_string_range() {
7777
ok(it->start == 18 && it->end == 30, "add string range: interval is [18,30]");
7878
}
7979

80+
/**
81+
* @brief add() with malformed string ranges must not create a trxid 0 interval.
82+
*/
83+
static void test_add_invalid_string_range() {
84+
GTID_Set gs;
85+
86+
ok(!gs.add(UUID_A, "abc"), "add invalid C string range: rejects non-numeric input");
87+
ok(!gs.add(UUID_A, "10-abc"), "add invalid C string range: rejects malformed end");
88+
ok(!gs.add(UUID_A, "10-20x"), "add invalid C string range: rejects trailing characters");
89+
ok(gs.map.empty(), "add invalid C string range: no UUID entry created");
90+
ok(!gs.has_gtid(UUID_A, 0), "add invalid C string range: trxid 0 was not added");
91+
}
92+
8093
/**
8194
* @brief add() with explicit start, end parameters.
8295
*/
@@ -299,12 +312,13 @@ static void test_copy() {
299312
}
300313

301314
int main() {
302-
plan(62);
315+
plan(67);
303316

304317
test_add_interval(); // 8 assertions
305318
test_add_trxid(); // 2 assertions
306319
test_add_cstring_range(); // 5 assertions
307320
test_add_string_range(); // 3 assertions
321+
test_add_invalid_string_range(); // 5 assertions
308322
test_add_start_end(); // 3 assertions
309323
test_add_multi_uuid(); // 3 assertions
310324
test_add_consecutive_trxid(); // 2 assertions

0 commit comments

Comments
 (0)