Skip to content

Commit fb92b3b

Browse files
committed
sql_acl: enforce bounded parsing for authentication packet fields
Replace unbounded string parsing (strlen/strend) with bounded checks to ensure NUL-terminated fields are validated within packet bounds. This prevents potential out-of-bounds reads when handling malformed authentication packets missing in-packet terminators. Add a regression test to verify safe handling of invalid authentication inputs and ensure no regression in normal authentication flow.
1 parent 96b3dd0 commit fb92b3b

3 files changed

Lines changed: 183 additions & 29 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#
2+
# Regression test: ensure authentication parsing handles invalid inputs safely
3+
# and does not affect subsequent valid connections.
4+
#
5+
DROP USER IF EXISTS bounded_ok@localhost;
6+
CREATE USER bounded_ok@localhost IDENTIFIED BY 'bounded_ok_pwd';
7+
#
8+
# Invalid authentication attempts must fail cleanly.
9+
#
10+
ERROR 28000: Access denied for user 'bounded_missing'@'localhost' (using password: YES)
11+
ERROR 28000: Access denied for user 'bounded_ok'@'localhost' (using password: YES)
12+
ERROR 28000: Access denied for user ''@'localhost' (using password: YES)
13+
#
14+
# Valid authentication still works after failures.
15+
#
16+
connection ok_conn1;
17+
SELECT 1;
18+
1
19+
1
20+
disconnect ok_conn1;
21+
connection ok_conn2;
22+
SELECT 1;
23+
1
24+
1
25+
disconnect ok_conn2;
26+
connection default;
27+
DROP USER bounded_ok@localhost;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--source include/not_embedded.inc
2+
3+
--echo #
4+
--echo # Regression test: ensure authentication parsing handles invalid inputs safely
5+
--echo # and does not affect subsequent valid connections.
6+
--echo #
7+
8+
--disable_warnings
9+
DROP USER IF EXISTS bounded_ok@localhost;
10+
--enable_warnings
11+
CREATE USER bounded_ok@localhost IDENTIFIED BY 'bounded_ok_pwd';
12+
13+
--echo #
14+
--echo # Invalid authentication attempts must fail cleanly.
15+
--echo #
16+
17+
--error ER_ACCESS_DENIED_ERROR
18+
connect (fail_unknown_user,localhost,bounded_missing,bounded_ok_pwd,test);
19+
20+
--error ER_ACCESS_DENIED_ERROR
21+
connect (fail_wrong_password,localhost,bounded_ok,wrong_pwd,test);
22+
23+
--error ER_ACCESS_DENIED_ERROR
24+
connect (fail_empty_username,localhost,,wrong_pwd,test);
25+
26+
--echo #
27+
--echo # Valid authentication still works after failures.
28+
--echo #
29+
30+
connect (ok_conn1,localhost,bounded_ok,bounded_ok_pwd,test);
31+
connection ok_conn1;
32+
SELECT 1;
33+
disconnect ok_conn1;
34+
35+
connect (ok_conn2,localhost,bounded_ok,bounded_ok_pwd,test);
36+
connection ok_conn2;
37+
SELECT 1;
38+
disconnect ok_conn2;
39+
40+
connection default;
41+
DROP USER bounded_ok@localhost;

sql/sql_acl.cc

Lines changed: 115 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14171,6 +14171,22 @@ read_client_connect_attrs(char **ptr, char *end, THD* thd)
1417114171
return false;
1417214172
}
1417314173

14174+
/*
14175+
Protocol uses NUL-terminated fields; ensure terminator is within packet bounds
14176+
before computing length to avoid relying on implicit assumptions.
14177+
*/
14178+
static size_t bounded_strnlen(const char *start, const char *end)
14179+
{
14180+
const char *p= start;
14181+
while (p < end)
14182+
{
14183+
if (*p == '\0')
14184+
return (size_t)(p - start);
14185+
++p;
14186+
}
14187+
return (size_t)-1;
14188+
}
14189+
1417414190
#endif
1417514191

1417614192
/* the packet format is described in send_change_user_packet() */
@@ -14181,17 +14197,24 @@ static bool parse_com_change_user_packet(MPVIO_EXT *mpvio, uint packet_length)
1418114197
Security_context *sctx= thd->security_ctx;
1418214198

1418314199
char *user= (char*) net->read_pos;
14184-
char *end= user + packet_length;
14185-
/* Safe because there is always a trailing \0 at the end of the packet */
14186-
char *passwd= strend(user) + 1;
14187-
uint user_len= (uint)(passwd - user - 1);
14200+
char *packet_end= user + packet_length;
14201+
/* Ensure user field is NUL-terminated within packet bounds */
14202+
size_t user_len_sz= bounded_strnlen(user, packet_end);
14203+
if (user_len_sz == (size_t)-1)
14204+
{
14205+
my_message(ER_UNKNOWN_COM_ERROR, ER_THD(thd, ER_UNKNOWN_COM_ERROR),
14206+
MYF(0));
14207+
DBUG_RETURN(1);
14208+
}
14209+
char *passwd= user + user_len_sz + 1;
14210+
uint user_len= (uint)user_len_sz;
1418814211
char *db= passwd;
1418914212
char db_buff[SAFE_NAME_LEN + 1]; // buffer to store db in utf8
1419014213
char user_buff[USERNAME_LENGTH + 1]; // buffer to store user in utf8
1419114214
uint dummy_errors;
1419214215
DBUG_ENTER ("parse_com_change_user_packet");
1419314216

14194-
if (passwd >= end)
14217+
if (passwd >= packet_end)
1419514218
{
1419614219
my_message(ER_UNKNOWN_COM_ERROR, ER_THD(thd, ER_UNKNOWN_COM_ERROR),
1419714220
MYF(0));
@@ -14208,26 +14231,45 @@ static bool parse_com_change_user_packet(MPVIO_EXT *mpvio, uint packet_length)
1420814231
Cast *passwd to an unsigned char, so that it doesn't extend the sign for
1420914232
*passwd > 127 and become 2**32-127+ after casting to uint.
1421014233
*/
14211-
uint passwd_len= (thd->client_capabilities & CLIENT_SECURE_CONNECTION ?
14212-
(uchar) (*passwd++) : (uint)strlen(passwd));
14234+
uint passwd_len;
14235+
if (thd->client_capabilities & CLIENT_SECURE_CONNECTION)
14236+
passwd_len= (uchar)(*passwd++);
14237+
else
14238+
{
14239+
size_t passwd_len_sz= bounded_strnlen(passwd, packet_end);
14240+
if (passwd_len_sz == (size_t)-1)
14241+
{
14242+
my_message(ER_UNKNOWN_COM_ERROR, ER_THD(thd, ER_UNKNOWN_COM_ERROR),
14243+
MYF(0));
14244+
DBUG_RETURN(1);
14245+
}
14246+
passwd_len= (uint)passwd_len_sz;
14247+
}
1421314248

1421414249
db+= passwd_len + 1;
1421514250
/*
1421614251
Database name is always NUL-terminated, so in case of empty database
1421714252
the packet must contain at least the trailing '\0'.
1421814253
*/
14219-
if (db >= end)
14254+
if (db >= packet_end)
1422014255
{
1422114256
my_message(ER_UNKNOWN_COM_ERROR, ER_THD(thd, ER_UNKNOWN_COM_ERROR),
1422214257
MYF(0));
1422314258
DBUG_RETURN (1);
1422414259
}
1422514260

14226-
size_t db_len= strlen(db);
14261+
/* Ensure db field is NUL-terminated within packet bounds */
14262+
size_t db_len= bounded_strnlen(db, packet_end);
14263+
if (db_len == (size_t)-1)
14264+
{
14265+
my_message(ER_UNKNOWN_COM_ERROR, ER_THD(thd, ER_UNKNOWN_COM_ERROR),
14266+
MYF(0));
14267+
DBUG_RETURN(1);
14268+
}
1422714269

1422814270
char *next_field= db + db_len + 1;
1422914271

14230-
if (next_field + 1 < end)
14272+
if (next_field + 1 < packet_end)
1423114273
{
1423214274
if (thd_init_client_charset(thd, uint2korr(next_field)))
1423314275
DBUG_RETURN(1);
@@ -14275,14 +14317,24 @@ static bool parse_com_change_user_packet(MPVIO_EXT *mpvio, uint packet_length)
1427514317
LEX_CSTRING client_plugin;
1427614318
if (thd->client_capabilities & CLIENT_PLUGIN_AUTH)
1427714319
{
14278-
if (next_field >= end)
14320+
client_plugin.str= "";
14321+
client_plugin.length= 0;
14322+
14323+
/* Only parse plugin if data remains within packet */
14324+
if (next_field < packet_end)
1427914325
{
14280-
my_message(ER_UNKNOWN_COM_ERROR, ER_THD(thd, ER_UNKNOWN_COM_ERROR),
14281-
MYF(0));
14282-
DBUG_RETURN(1);
14326+
/* Ensure plugin field is NUL-terminated within packet bounds */
14327+
size_t plugin_len= bounded_strnlen(next_field, packet_end);
14328+
if (plugin_len == (size_t)-1)
14329+
{
14330+
my_message(ER_UNKNOWN_COM_ERROR, ER_THD(thd, ER_UNKNOWN_COM_ERROR),
14331+
MYF(0));
14332+
DBUG_RETURN(1);
14333+
}
14334+
client_plugin.str= next_field;
14335+
client_plugin.length= plugin_len;
14336+
next_field+= plugin_len + 1;
1428314337
}
14284-
client_plugin= Lex_cstring_strlen(next_field);
14285-
next_field+= client_plugin.length + 1;
1428614338
}
1428714339
else
1428814340
{
@@ -14301,7 +14353,7 @@ static bool parse_com_change_user_packet(MPVIO_EXT *mpvio, uint packet_length)
1430114353
}
1430214354

1430314355
if ((thd->client_capabilities & CLIENT_CONNECT_ATTRS) &&
14304-
read_client_connect_attrs(&next_field, end, thd))
14356+
read_client_connect_attrs(&next_field, packet_end, thd))
1430514357
{
1430614358
my_message(ER_UNKNOWN_COM_ERROR, ER_THD(thd, ER_UNKNOWN_COM_ERROR),
1430714359
MYF(0));
@@ -14453,24 +14505,32 @@ static ulong parse_client_handshake_packet(MPVIO_EXT *mpvio,
1445314505
end= (char*) net->read_pos+5;
1445414506
}
1445514507

14456-
if (end >= (char*) net->read_pos+ pkt_len +2)
14508+
const char *packet_end= (const char*)net->read_pos + pkt_len;
14509+
14510+
if (end >= packet_end + 2)
1445714511
return packet_error;
1445814512

1445914513
if (thd->client_capabilities & CLIENT_IGNORE_SPACE)
1446014514
thd->variables.sql_mode|= MODE_IGNORE_SPACE;
1446114515
if (thd->client_capabilities & CLIENT_INTERACTIVE)
1446214516
thd->variables.net_wait_timeout= thd->variables.net_interactive_timeout;
1446314517

14464-
if (end >= (char*) net->read_pos+ pkt_len +2)
14518+
if (end >= packet_end + 2)
1446514519
return packet_error;
1446614520

1446714521
if ((thd->client_capabilities & CLIENT_TRANSACTIONS) &&
1446814522
opt_using_transactions)
1446914523
net->return_status= &thd->server_status;
1447014524

1447114525
char *user= end;
14472-
char *passwd= strend(user)+1;
14473-
size_t user_len= (size_t)(passwd - user - 1), db_len;
14526+
14527+
/* Ensure user field is NUL-terminated within packet bounds */
14528+
size_t user_len= bounded_strnlen(user, packet_end);
14529+
size_t db_len;
14530+
if (user_len == (size_t)-1)
14531+
return packet_error;
14532+
14533+
char *passwd= user + user_len + 1;
1447414534
char *db= passwd;
1447514535
char user_buff[USERNAME_LENGTH + 1]; // buffer to store user in utf8
1447614536
uint dummy_errors;
@@ -14489,7 +14549,10 @@ static ulong parse_client_handshake_packet(MPVIO_EXT *mpvio,
1448914549

1449014550
if (!(thd->client_capabilities & CLIENT_SECURE_CONNECTION))
1449114551
{
14492-
passwd_len= strlen(passwd);
14552+
passwd_len= bounded_strnlen(passwd, packet_end);
14553+
if (passwd_len == (size_t)-1)
14554+
return packet_error;
14555+
1449314556
db= thd->client_capabilities & CLIENT_CONNECT_WITH_DB ?
1449414557
passwd + passwd_len + 1 : 0; /* +1 to skip null terminator */
1449514558
}
@@ -14504,7 +14567,8 @@ static ulong parse_client_handshake_packet(MPVIO_EXT *mpvio,
1450414567
else
1450514568
{
1450614569
ulonglong len= safe_net_field_length_ll((uchar**)&passwd,
14507-
net->read_pos + pkt_len - (uchar*)passwd);
14570+
(const uchar*)packet_end -
14571+
(uchar*)passwd);
1450814572
if (len > pkt_len)
1450914573
return packet_error;
1451014574
passwd_len= (size_t)len;
@@ -14513,14 +14577,36 @@ static ulong parse_client_handshake_packet(MPVIO_EXT *mpvio,
1451314577
}
1451414578

1451514579
if (passwd == NULL ||
14516-
passwd + passwd_len + MY_TEST(db) > (char*) net->read_pos + pkt_len)
14580+
passwd + passwd_len + MY_TEST(db) > packet_end)
1451714581
return packet_error;
1451814582

14519-
/* strlen() can't be easily deleted without changing protocol */
14520-
db_len= safe_strlen(db);
14583+
if (db)
14584+
{
14585+
db_len= bounded_strnlen(db, packet_end);
14586+
if (db_len == (size_t)-1)
14587+
return packet_error;
14588+
}
14589+
else
14590+
db_len= 0;
1452114591

1452214592
char *next_field= passwd + passwd_len + (db ? db_len + 1 : 0);
14523-
Lex_ident_plugin client_plugin= Lex_cstring_strlen(next_field);
14593+
size_t client_plugin_len= 0;
14594+
Lex_ident_plugin client_plugin;
14595+
client_plugin.str = "";
14596+
client_plugin.length = 0;
14597+
14598+
/* Only parse plugin if data remains within packet */
14599+
if (next_field < packet_end)
14600+
{
14601+
/* Ensure plugin field is NUL-terminated within packet bounds */
14602+
size_t plugin_len= bounded_strnlen(next_field, packet_end);
14603+
if (plugin_len == (size_t)-1)
14604+
return packet_error;
14605+
14606+
client_plugin.str= next_field;
14607+
client_plugin.length= plugin_len;
14608+
client_plugin_len= plugin_len;
14609+
}
1452414610

1452514611
/*
1452614612
Since 4.1 all database names are stored in utf8
@@ -14580,9 +14666,9 @@ static ulong parse_client_handshake_packet(MPVIO_EXT *mpvio,
1458014666
return packet_error;
1458114667

1458214668
if ((thd->client_capabilities & CLIENT_PLUGIN_AUTH) &&
14583-
(client_plugin.str < (char *)net->read_pos + pkt_len))
14669+
(client_plugin.str < packet_end))
1458414670
{
14585-
next_field+= strlen(next_field) + 1;
14671+
next_field+= client_plugin_len + 1;
1458614672
}
1458714673
else
1458814674
{

0 commit comments

Comments
 (0)