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

[CONC-648] Do not trust error packets received from the server prior to TLS handshake completion #223

Merged
merged 3 commits into from
Dec 21, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 24 additions & 18 deletions libmariadb/mariadb_lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -241,18 +241,30 @@ ma_net_safe_read(MYSQL *mysql)
}
goto restart;
}
net->last_errno= last_errno;
if (pos[0]== '#')
if (last_errno >= CR_MIN_ERROR && last_errno <= CR_MAX_ERROR ||
Copy link
Collaborator

Choose a reason for hiding this comment

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

How about:
if (IS_MYSQL_ERROR(last_errno) || IS_MARIADB_ERROR(last_errno)) ?

Copy link
Member

Choose a reason for hiding this comment

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

This is needed to fix travis builds, otherwise the code doesn't compile with -Werror. But I don't want to drag this any longer, I'll merge and fix afterwards with a separate commit

Copy link
Contributor Author

@dlenski dlenski Dec 21, 2023

Choose a reason for hiding this comment

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

How about:
if (IS_MYSQL_ERROR(last_errno) || IS_MARIADB_ERROR(last_errno)) ?

Makes sense to simplify the code, but per the definition of the IS_MYSQL_ERROR macro, this would not be 100% equivalent.

  1. Current version rejects error codes [2000, 2999] + [5000, 5999].
  2. @9EOR9's proposed replacement would reject only error codes [2000, 2061] + [5000, 5023].

Because these error codes are client-side-only, they should be equivalent as long as no one forgets to update CR_MYSQL_LAST_ERROR and CR_MARIADB_LAST_ERROR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Consider modifying the IS_MYSQL_ERROR and IS_MARIADB_ERROR macros so that they include the entire client-side error ranges, rather than depending on CR_MYSQL_LAST_ERROR and CR_MARIADB_LAST_ERROR being updated.

Copy link
Member

Choose a reason for hiding this comment

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

That wouldn't hurt, but I'll leave it to @9EOR9. It doesn't matter much whether we test the full possible range or the actual range only, All error codes within the range we test can be considered safe, not injected by MitM, but generated here in the client library. All codes outside of that range are unsafe (before SSL). So as long as the code never trusts error message numbers outside of the tested range, it should be good.

last_errno >= CER_MIN_ERROR && last_errno <= CER_MAX_ERROR)
{
ma_strmake(net->sqlstate, pos+1, SQLSTATE_LENGTH);
pos+= SQLSTATE_LENGTH + 1;
/* The server appears to have sent an error code within the
* range(s) of error codes that should only be generated
* client-side.
*/
my_set_error(mysql, CR_MALFORMED_PACKET, SQLSTATE_UNKNOWN, 0);
}
else
{
strncpy(net->sqlstate, SQLSTATE_UNKNOWN, SQLSTATE_LENGTH);
net->last_errno= last_errno;
if (pos[0]== '#')
{
ma_strmake(net->sqlstate, pos+1, SQLSTATE_LENGTH);
pos+= SQLSTATE_LENGTH + 1;
}
else
{
strncpy(net->sqlstate, SQLSTATE_UNKNOWN, SQLSTATE_LENGTH);
}
ma_strmake(net->last_error,(char*) pos,
min(len,sizeof(net->last_error)-1));
}
ma_strmake(net->last_error,(char*) pos,
min(len,sizeof(net->last_error)-1));
}
else
{
Expand Down Expand Up @@ -1792,6 +1804,11 @@ MYSQL *mthd_my_real_connect(MYSQL *mysql, const char *host, const char *user,
ER(CR_SERVER_LOST_EXTENDED),
"handshake: reading initial communication packet",
errno);
else if (mysql->options.use_ssl)
my_set_error(mysql, CR_CONNECTION_ERROR, SQLSTATE_UNKNOWN,
"Received error packet before completion of TLS handshake. "
"The authenticity of the following error cannot be verified:\n%d - %s",
mysql->net.last_errno, mysql->net.last_error);

goto error;
}
Expand All @@ -1803,17 +1820,6 @@ MYSQL *mthd_my_real_connect(MYSQL *mysql, const char *host, const char *user,
mysql->protocol_version= end[0];
end++;

/* Check if server sends an error */
if (mysql->protocol_version == 0XFF)
{
net_get_error(end, pkt_length - 1, net->last_error, sizeof(net->last_error),
&net->last_errno, net->sqlstate);
/* fix for bug #26426 */
if (net->last_errno == 1040)
memcpy(net->sqlstate, "08004", SQLSTATE_LENGTH);
goto error;
}

if (mysql->protocol_version < PROTOCOL_VERSION)
{
net->last_errno= CR_VERSION_ERROR;
Expand Down