Skip to content

Commit 42046c0

Browse files
Fix XML rendering crash with non-printable characters
Replace DATA_INVARIANT crash with proper escaping when non-printable characters appear in XML output. Characters valid in XML 1.0 (TAB, LF, CR) use numeric character references (e.g., &#9;). Characters invalid in XML 1.0 (null, other control chars, DEL) use C-style escape sequences (\0, \xNN). Backslashes are escaped as \\ to avoid ambiguity. Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
1 parent 7ff43f7 commit 42046c0

4 files changed

Lines changed: 144 additions & 14 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#include <assert.h>
2+
3+
int main(void)
4+
{
5+
const unsigned char *str = (unsigned char *)"\x00";
6+
assert(*str == '\x01');
7+
return 0;
8+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
CORE
2+
main.c
3+
--trace --xml-ui
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
VERIFICATION FAILED
7+
--
8+
XML does not support escaping non-printable character
9+
--
10+
Test that cbmc with --trace --xml-ui handles non-printable characters
11+
gracefully without crashing when they appear in counterexample traces.
12+
This test verifies that the fix for character 0x01 allows XML generation
13+
to complete successfully by using numeric character references.

src/util/xml.cpp

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -101,17 +101,34 @@ void xmlt::escape(const std::string &s, std::ostream &out)
101101
out << '\n';
102102
break;
103103

104-
case 0x9: // TAB
105-
case 0x7F: // DEL
104+
case 0x9: // TAB
106105
out << "&#" << std::to_string((unsigned char)ch) << ';';
107106
break;
108107

108+
case 0x7F: // DEL
109+
out << "\\x7f";
110+
break;
111+
109112
default:
110-
DATA_INVARIANT(
111-
static_cast<unsigned char>(ch) >= 32u,
112-
"XML does not support escaping non-printable character " +
113-
std::to_string((unsigned char)ch));
114-
out << ch;
113+
// Non-printable characters below 0x20 (other than TAB, LF, CR which
114+
// are handled above) are not valid in XML 1.0, not even as numeric
115+
// character references. Encode them as C-style escape sequences.
116+
if(static_cast<unsigned char>(ch) < 32u)
117+
{
118+
if(ch == '\0')
119+
out << "\\0";
120+
else
121+
out << "\\x" << std::hex << (unsigned int)(unsigned char)ch
122+
<< std::dec;
123+
}
124+
else if(ch == '\\')
125+
{
126+
out << "\\\\";
127+
}
128+
else
129+
{
130+
out << ch;
131+
}
115132
}
116133
}
117134
}
@@ -143,16 +160,33 @@ void xmlt::escape_attribute(const std::string &s, std::ostream &out)
143160
case 0x9: // TAB
144161
case 0xA: // LF
145162
case 0xD: // CR
146-
case 0x7F: // DEL
147163
out << "&#" << std::to_string((unsigned char)ch) << ';';
148164
break;
149165

166+
case 0x7F: // DEL
167+
out << "\\x7f";
168+
break;
169+
150170
default:
151-
DATA_INVARIANT(
152-
static_cast<unsigned char>(ch) >= 32u,
153-
"XML does not support escaping non-printable character " +
154-
std::to_string((unsigned char)ch));
155-
out << ch;
171+
// Non-printable characters below 0x20 (other than TAB, LF, CR which
172+
// are handled above) are not valid in XML 1.0, not even as numeric
173+
// character references. Encode them as C-style escape sequences.
174+
if(static_cast<unsigned char>(ch) < 32u)
175+
{
176+
if(ch == '\0')
177+
out << "\\0";
178+
else
179+
out << "\\x" << std::hex << (unsigned int)(unsigned char)ch
180+
<< std::dec;
181+
}
182+
else if(ch == '\\')
183+
{
184+
out << "\\\\";
185+
}
186+
else
187+
{
188+
out << ch;
189+
}
156190
}
157191
}
158192
}

unit/util/xml.cpp

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ Author: Thomas Kiley
66
77
\*******************************************************************/
88

9-
#include <testing-utils/use_catch.h>
109
#include <util/xml.h>
1110

11+
#include <testing-utils/use_catch.h>
12+
13+
#include <sstream>
14+
1215
TEST_CASE("xml_equal", "[core][util][xml]")
1316
{
1417
SECTION("Empty xml")
@@ -97,3 +100,75 @@ TEST_CASE("xml_equal", "[core][util][xml]")
97100
}
98101
}
99102
}
103+
104+
TEST_CASE("xml_escape_nonprintable", "[core][util][xml]")
105+
{
106+
SECTION("Escaping non-printable characters in attributes")
107+
{
108+
xmlt node{"test"};
109+
node.set_attribute("value", std::string("\x00\x01\x02\x1F", 4));
110+
111+
std::ostringstream out;
112+
node.output(out);
113+
114+
std::string result = out.str();
115+
// Characters invalid in XML 1.0 are encoded as C-style escapes
116+
REQUIRE(result.find("\\0") != std::string::npos);
117+
REQUIRE(result.find("\\x1") != std::string::npos);
118+
REQUIRE(result.find("\\x2") != std::string::npos);
119+
REQUIRE(result.find("\\x1f") != std::string::npos);
120+
}
121+
122+
SECTION("Escaping non-printable characters in data")
123+
{
124+
xmlt node{"test"};
125+
node.data = std::string("\x00\x01\x02\x1F", 4);
126+
127+
std::ostringstream out;
128+
node.output(out);
129+
130+
std::string result = out.str();
131+
// Characters invalid in XML 1.0 are encoded as C-style escapes
132+
REQUIRE(result.find("\\0") != std::string::npos);
133+
REQUIRE(result.find("\\x1") != std::string::npos);
134+
REQUIRE(result.find("\\x2") != std::string::npos);
135+
REQUIRE(result.find("\\x1f") != std::string::npos);
136+
}
137+
138+
SECTION("Valid XML 1.0 control characters use numeric references")
139+
{
140+
xmlt node{"test"};
141+
node.set_attribute("value", std::string("\x09", 1));
142+
143+
std::ostringstream out;
144+
node.output(out);
145+
146+
std::string result = out.str();
147+
// TAB is valid in XML 1.0 and uses numeric character reference
148+
REQUIRE(result.find("&#9;") != std::string::npos);
149+
}
150+
151+
SECTION("Backslashes are escaped")
152+
{
153+
xmlt node{"test"};
154+
node.set_attribute("value", "back\\slash");
155+
156+
std::ostringstream out;
157+
node.output(out);
158+
159+
std::string result = out.str();
160+
REQUIRE(result.find("back\\\\slash") != std::string::npos);
161+
}
162+
163+
SECTION("Standard printable characters unchanged")
164+
{
165+
xmlt node{"test"};
166+
node.set_attribute("value", "Hello World!");
167+
168+
std::ostringstream out;
169+
node.output(out);
170+
171+
std::string result = out.str();
172+
REQUIRE(result.find("Hello World!") != std::string::npos);
173+
}
174+
}

0 commit comments

Comments
 (0)