Skip to content

Commit 6571a06

Browse files
Fix heap-buffer-overflow in query_string when parsing malformed '%'Fix heap-buffer-overflow in query_string for malformed '%' (#1134)
* Fix heap-buffer-overflow in query_string for malformed '%'
1 parent b8c021a commit 6571a06

2 files changed

Lines changed: 52 additions & 7 deletions

File tree

include/crow/query_string.h

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,23 +64,33 @@ inline int qs_strncmp(const char * s, const char * qs, size_t n)
6464
if ( u1 == '+' ) { u1 = ' '; }
6565
if ( u1 == '%' ) // easier/safer than scanf
6666
{
67-
unyb = static_cast<unsigned char>(*s++);
68-
lnyb = static_cast<unsigned char>(*s++);
69-
if ( CROW_QS_ISHEX(unyb) && CROW_QS_ISHEX(lnyb) )
67+
// Check that next two chars exist and are valid hex before reading
68+
if ( CROW_QS_ISHEX(s[0]) && CROW_QS_ISHEX(s[1]) )
69+
{
70+
unyb = static_cast<unsigned char>(*s++);
71+
lnyb = static_cast<unsigned char>(*s++);
7072
u1 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb);
73+
}
7174
else
75+
{
7276
u1 = '\0';
77+
}
7378
}
7479

7580
if ( u2 == '+' ) { u2 = ' '; }
7681
if ( u2 == '%' ) // easier/safer than scanf
7782
{
78-
unyb = static_cast<unsigned char>(*qs++);
79-
lnyb = static_cast<unsigned char>(*qs++);
80-
if ( CROW_QS_ISHEX(unyb) && CROW_QS_ISHEX(lnyb) )
83+
// Check that next two chars exist and are valid hex before reading
84+
if ( CROW_QS_ISHEX(qs[0]) && CROW_QS_ISHEX(qs[1]) )
85+
{
86+
unyb = static_cast<unsigned char>(*qs++);
87+
lnyb = static_cast<unsigned char>(*qs++);
8188
u2 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb);
89+
}
8290
else
91+
{
8392
u2 = '\0';
93+
}
8494
}
8595

8696
if ( u1 != u2 )
@@ -150,7 +160,9 @@ inline int qs_decode(char * qs)
150160
if ( qs[j] == '+' ) { qs[i] = ' '; }
151161
else if ( qs[j] == '%' ) // easier/safer than scanf
152162
{
153-
if ( ! CROW_QS_ISHEX(qs[j+1]) || ! CROW_QS_ISHEX(qs[j+2]) )
163+
// Check bounds before reading: ensure j+1 and j+2 are within string
164+
if ( qs[j+1] == '\0' || qs[j+2] == '\0' ||
165+
! CROW_QS_ISHEX(qs[j+1]) || ! CROW_QS_ISHEX(qs[j+2]) )
154166
{
155167
qs[i] = '\0';
156168
return i;

tests/query_string_tests.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,37 @@ TEST_CASE( "query string keys" )
4242
return key == entry.first;});
4343
REQUIRE(exist == true);
4444
}
45+
}
46+
47+
TEST_CASE( "malformed percent encoding - trailing percent" )
48+
{
49+
// URL ending with '%' should not cause heap overflow (Issue #1126)
50+
crow::query_string qs1("https://example.com/asdf?%");
51+
auto keys1 = qs1.keys();
52+
// Should handle gracefully without crashing
53+
if (!keys1.empty()) {
54+
(void)qs1.get(keys1[0]);
55+
}
56+
57+
// URL with single hex digit after %
58+
crow::query_string qs2("https://example.com/?key=%2");
59+
auto keys2 = qs2.keys();
60+
if (!keys2.empty()) {
61+
(void)qs2.get(keys2[0]);
62+
}
63+
64+
// URL with % followed by non-hex characters
65+
crow::query_string qs3("https://example.com/?key=%GG");
66+
auto keys3 = qs3.keys();
67+
if (!keys3.empty()) {
68+
(void)qs3.get(keys3[0]);
69+
}
70+
71+
// Valid percent encoding should still work
72+
crow::query_string qs4("https://example.com/?key=%20value");
73+
auto val = qs4.get("key");
74+
REQUIRE(val != nullptr);
75+
REQUIRE(std::string(val) == " value");
76+
77+
REQUIRE(true); // Test passes if no crash/sanitizer error
4578
}

0 commit comments

Comments
 (0)