Skip to content

Commit 9f08067

Browse files
committed
Bug#23707238 - PROTOBUF LIMITS THE NUMBER OF NESTED OBJECTS TO 100 RECURSIONS
Description: When X Plugin receives X Protocol message that contains 101 nested objects, the decoding operation fails and client received following error: "Invalid message" and gets disconnected. Analysis: Decoding failure is caused by Protobuf which limits the number of nested objects. There isn't any way to verify the cause of the failure, which leaves us with a generic message. When decoding this message manualy (by "protoc") it doesn't give any usefull information about the cause. Fix: Fix sets the limit to 100 (in explicit to ensure that we do not overwrite the stack) It is done by calling following method: google::protobuf::io::CodedInputStream::SetRecursionLimit Used a workaround for checking the error. When decoding failed, plugin call DecrementRecursionDepth and IncrementRecursionDepth on CodedInputStream, if the increment fails this mean that the limit was reached and correspondin error information is send back. RB: 13186 Reviewed-by: Grzegorz Szwarc <[email protected]> Reviewed-by: Andrzej Religa <[email protected]>
1 parent c47f2b7 commit 9f08067

File tree

6 files changed

+155
-12
lines changed

6 files changed

+155
-12
lines changed

rapid/plugin/x/mysqlxtest_src/mysqlxtest.cc

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -822,8 +822,8 @@ class Command
822822
m_commands["enablessl"] = &Command::cmd_enablessl;
823823
m_commands["sleep "] = &Command::cmd_sleep;
824824
m_commands["login "] = &Command::cmd_login;
825-
m_commands["stmtadmin "] = &Command::cmd_stmt_admin;
826-
m_commands["stmtsql "] = &Command::cmd_stmt_sql;
825+
m_commands["stmtadmin "] = &Command::cmd_stmtadmin;
826+
m_commands["stmtsql "] = &Command::cmd_stmtsql;
827827
m_commands["loginerror "] = &Command::cmd_loginerror;
828828
m_commands["repeat "] = &Command::cmd_repeat;
829829
m_commands["endrepeat"] = &Command::cmd_endrepeat;
@@ -1149,7 +1149,7 @@ class Command
11491149
return Continue;
11501150
}
11511151

1152-
Result cmd_stmt_sql(Execution_context &context, const std::string &args)
1152+
Result cmd_stmtsql(Execution_context &context, const std::string &args)
11531153
{
11541154
Mysqlx::Sql::StmtExecute stmt;
11551155

@@ -1165,7 +1165,7 @@ class Command
11651165
}
11661166

11671167

1168-
Result cmd_stmt_admin(Execution_context &context, const std::string &args)
1168+
Result cmd_stmtadmin(Execution_context &context, const std::string &args)
11691169
{
11701170
std::string tmp = args;
11711171
replace_variables(tmp);
@@ -1296,6 +1296,9 @@ class Command
12961296
variable_name = argl[1];
12971297
}
12981298

1299+
// Allow use of variables as a source of number of iterations
1300+
replace_variables(argl[0]);
1301+
12991302
Loop_do loop = {context.m_stream.tellg(), atoi(argl[0].c_str()), 0, variable_name};
13001303

13011304
m_loop_stack.push_back(loop);

rapid/plugin/x/ngs/src/protocol_decoder.cc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ Message *Message_decoder::alloc_message(int8_t type, Error_code &ret_error, bool
9898

9999
Error_code Message_decoder::parse(Request &request)
100100
{
101+
const int max_recursion_limit = 100;
101102
bool msg_is_shared;
102103
Error_code ret_error;
103104
Message *message = alloc_message(request.get_type(), ret_error, msg_is_shared);
@@ -109,12 +110,29 @@ Error_code Message_decoder::parse(Request &request)
109110
static_cast<int>(buffer.length()));
110111
// variable 'mysqlx_max_allowed_packet' has been checked when buffer was filling by data
111112
stream.SetTotalBytesLimit(static_cast<int>(buffer.length()), -1 /*no warnings*/);
113+
// Protobuf limits the number of nested objects when decoding messages
114+
// lets set the value in explicit way (to ensure that is set accordingly with
115+
// out stack size)
116+
//
117+
// Protobuf doesn't print a readable error after reaching the limit
118+
// thus in case of failure we try to validate the limit by decrementing and
119+
// incrementing the value & checking result for failure
120+
stream.SetRecursionLimit(max_recursion_limit);
121+
112122
message->ParseFromCodedStream(&stream);
113123

114124
if (!message->IsInitialized())
115125
{
116126
log_debug("Error parsing message of type %i: %s",
117127
request.get_type(), message->InitializationErrorString().c_str());
128+
129+
//Workaraound
130+
stream.DecrementRecursionDepth();
131+
if (!stream.IncrementRecursionDepth())
132+
{
133+
return Error(ER_X_BAD_MESSAGE, "X Protocol message recursion limit (%i) exceeded", max_recursion_limit);
134+
}
135+
118136
if (!msg_is_shared)
119137
delete message;
120138
message = NULL;

rapid/plugin/x/protocol/mysqlx_datatypes.proto

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,14 @@ message Scalar {
3131
required bytes value = 1;
3232
optional uint64 collation = 2;
3333
};
34-
35-
// an opaque octet sequence, with an optional content_type
36-
// See ``Mysqlx.Resultset.ColumnMetadata`` for list of known values.
37-
message Octets {
38-
required bytes value = 1;
39-
optional uint32 content_type = 2;
40-
};
41-
34+
35+
// an opaque octet sequence, with an optional content_type
36+
// See ``Mysqlx.Resultset.ColumnMetadata`` for list of known values.
37+
message Octets {
38+
required bytes value = 1;
39+
optional uint32 content_type = 2;
40+
};
41+
4242
enum Type {
4343
V_SINT = 1;
4444
V_UINT = 2;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
install plugin mysqlx soname "mysqlx.so";
2+
call mtr.add_suppression("Plugin mysqlx reported: .Failed at SSL configuration: .SSL context is not usable without certificate and private key..");
3+
call mtr.add_suppression("Plugin mysqlx reported: .SSL_CTX_load_verify_locations failed.");
4+
5+
command ok
6+
connecting...
7+
active session is now 'test_different_messages'
8+
Try to use value greater than the default limit 50 /2 => 25
9+
doc
10+
command ok
11+
Try to use value equal than the X Protocol limit 100 /2 => 50
12+
doc
13+
command ok
14+
Try to use value greater than the X Protocol limit 102 /2 => 51
15+
Got expected error: X Protocol message recursion limit (100) exceeded (code 5000)
16+
aborting session test_different_messages
17+
switched to session default
18+
Mysqlx.Ok {
19+
msg: "bye!"
20+
}
21+
ok
22+
uninstall plugin mysqlx;
23+
DROP TABLE IF EXISTS coll;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--plugin_dir=$MYSQLXPLUGIN_DIR
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
## Preamble
2+
--source ../include/xplugin_preamble.inc
3+
4+
## TEST STARTS HERE
5+
--write_file $MYSQL_TMP_DIR/mysqlx-update_itemremove.tmp
6+
-->stmtadmin create_collection {"schema":"test", "name":"coll"}
7+
-->recvresult
8+
9+
## It tests the limits of protobuf, we do not need any data in the collection
10+
11+
12+
-->macro Send_message_with_repeated_nested_objects %OBJECTS_TO_NEST%
13+
-->varlet %NESTED_OBJECTS%
14+
15+
-->varlet %OBJECTS_TO_REPEAT% %OBJECTS_TO_NEST%
16+
-->varinc %OBJECTS_TO_REPEAT% -3
17+
18+
-->repeat %OBJECTS_TO_REPEAT% %ITERATION%
19+
-->varlet %OBJECT_NUMBER% %ITERATION%
20+
-->varinc %OBJECT_NUMBER% 4
21+
-->varlet %NESTED_OBJECTS% %NESTED_OBJECTS% value { type: ARRAY array { value { type: LITERAL literal { type: V_SINT v_signed_int: %OBJECT_NUMBER% } }
22+
-->endrepeat
23+
24+
-->repeat %OBJECTS_TO_REPEAT%
25+
-->varlet %NESTED_OBJECTS% %NESTED_OBJECTS% } }
26+
-->endrepeat
27+
28+
-->quiet
29+
Mysqlx.Crud.Find {
30+
collection {
31+
name: "coll"
32+
schema: "test"
33+
}
34+
data_model: DOCUMENT
35+
criteria {
36+
type: OPERATOR
37+
operator {
38+
name: "=="
39+
param {
40+
type: IDENT
41+
identifier {
42+
document_path {
43+
type: MEMBER
44+
value: "ARR0"
45+
}
46+
}
47+
}
48+
param {
49+
type: ARRAY
50+
array {
51+
value {
52+
type: LITERAL
53+
literal {
54+
type: V_SINT
55+
v_signed_int: 3
56+
}
57+
}
58+
59+
%NESTED_OBJECTS%
60+
61+
62+
}
63+
64+
}
65+
}
66+
}
67+
}
68+
-->noquiet
69+
-->endmacro
70+
71+
## Current recursion limit is set to 100
72+
## each repeated message in bellow macro contains two messages
73+
## (it increases the current depth by 2)
74+
-->newsession test_different_messages root
75+
-->echo Try to use value greater than the default limit 50 /2 => 25
76+
-->callmacro Send_message_with_repeated_nested_objects 25
77+
-->recvresult
78+
79+
-->echo Try to use value equal than the X Protocol limit 100 /2 => 50
80+
-->callmacro Send_message_with_repeated_nested_objects 50
81+
-->recvresult
82+
83+
-->echo Try to use value greater than the X Protocol limit 102 /2 => 51
84+
-->callmacro Send_message_with_repeated_nested_objects 51
85+
-->expecterror 5000
86+
-->recvresult
87+
-->closesession abort
88+
89+
EOF
90+
91+
--exec $MYSQLXTEST -u root --file=$MYSQL_TMP_DIR/mysqlx-update_itemremove.tmp 2>&1
92+
93+
## Postamble
94+
--remove_file $MYSQL_TMP_DIR/mysqlx-update_itemremove.tmp
95+
96+
uninstall plugin mysqlx;
97+
98+
DROP TABLE IF EXISTS coll;

0 commit comments

Comments
 (0)