Skip to content
Merged
Show file tree
Hide file tree
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
10 changes: 10 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,16 @@ set(IPCTOOL_SRC
src/clocks.h
src/cpubench.c
src/cpubench.h
src/crypto/aes.c
src/crypto/aes.h
src/crypto/chachapoly.c
src/crypto/chachapoly.h
src/crypto/gcm.c
src/crypto/gcm.h
src/crypto/hisi_cipher.c
src/crypto/hisi_cipher.h
src/cryptobench.c
src/cryptobench.h
src/dns.c
src/dns.h
src/ethernet.c
Expand Down
87 changes: 79 additions & 8 deletions src/cjson/cYAML.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <limits.h>
#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Expand Down Expand Up @@ -72,6 +73,50 @@ static bool strbuf_push(string_buffer *buf, const char *s) {
return true;
}

/* Length of the well-formed UTF-8 sequence starting at `c`, or 0 if what is
* there is not one.
*
* ipctool does not get to assume its input is UTF-8: U-Boot environments,
* sensor names and vendor strings are read as raw bytes off flash and passed
* through untranscoded, so a high byte may be latin-1, a truncated sequence,
* or nothing in particular. Valid sequences must survive unescaped (that is
* the point of a UTF-8 document format); invalid ones must not reach the
* output, or the whole stream stops parsing because of one bad byte in one
* value. Rejecting overlong forms, surrogates and out-of-range code points
* matters for the same reason -- a parser is entitled to refuse them. */
static int utf8_seq_len(const unsigned char *c) {
if (c[0] < 0x80)
return 1;

unsigned len;
uint32_t cp;
if ((c[0] & 0xe0) == 0xc0) {
len = 2;
cp = c[0] & 0x1fu;
} else if ((c[0] & 0xf0) == 0xe0) {
len = 3;
cp = c[0] & 0x0fu;
} else if ((c[0] & 0xf8) == 0xf0) {
len = 4;
cp = c[0] & 0x07u;
} else {
return 0; /* continuation byte or 0xfe/0xff, never a lead */
}

for (unsigned i = 1; i < len; i++) {
if ((c[i] & 0xc0) != 0x80)
return 0; /* also catches the terminator: 0 is not a continuation */
cp = (cp << 6) | (c[i] & 0x3fu);
}

static const uint32_t min_cp[5] = {0, 0, 0x80, 0x800, 0x10000};
if (cp < min_cp[len] || cp > 0x10ffff)
return 0; /* overlong, or past the last code point */
if (cp >= 0xd800 && cp <= 0xdfff)
return 0; /* surrogate half, not a scalar value */
return (int)len;
}

static bool print_string(string_buffer *buf, const char *s) {
if (!s || !*s) {
TRY(strbuf_push(buf, "\"\""));
Expand All @@ -81,12 +126,20 @@ static bool print_string(string_buffer *buf, const char *s) {
static const char *ESCAPES = "\"\\\b\f\n\r\t";
static const char *REPLACEMENTS = "\"\\bfnrt";

/* Every test here compares as `unsigned char`. `char` is signed on x86 and
* ARM alike, so a plain `*c < 32` was also true for every byte of a UTF-8
* sequence; those fell into the \u expansion below, overran its 10-byte
* scratch, and failed the whole print -- cYAML_Print returned NULL and
* ipctool printed nothing at all rather than one mangled string. */
bool needs_escaping = false;
for (const char *c = s; *c; c++) {
if (*c < 32 || *c == ':' || index(ESCAPES, *c) != NULL) {
for (const unsigned char *c = (const unsigned char *)s; *c;) {
const int seq = utf8_seq_len(c);
if (seq == 0 || *c < 32 || *c == ':' ||
index(ESCAPES, (char)*c) != NULL) {
needs_escaping = true;
break;
}
c += seq;
}

if (!needs_escaping) {
Expand All @@ -95,26 +148,44 @@ static bool print_string(string_buffer *buf, const char *s) {
}

TRY(strbuf_push(buf, "\""));
for (const char *c = s; *c; c++) {
char *found = index(ESCAPES, *c);
for (const unsigned char *c = (const unsigned char *)s; *c;) {
char *found = *c < 0x80 ? index(ESCAPES, (char)*c) : NULL;
if (found != NULL) {
char repl[] = "\\_";
repl[1] = REPLACEMENTS[found - ESCAPES];
TRY(strbuf_push(buf, repl));
c++;
continue;
}

const int seq = utf8_seq_len(c);
if (seq > 1) {
/* A valid multi-byte sequence goes out as it came in. */
for (int i = 0; i < seq; i++) {
char raw[] = "_";
raw[0] = (char)c[i];
TRY(strbuf_push(buf, raw));
}
c += seq;
continue;
}

if (*c < 32) {
/* Expand non-printable characters. */
if (seq == 0 || *c < 32) {
/* Control characters, and bytes that are not UTF-8 at all. \u00XX
* is a lossless spelling of the byte: it round-trips through a
* parser as U+0000..U+00FF and keeps the document readable. */
char repl[10];
TRY(snprintf(repl, sizeof(repl), "\\u%04x", *c) < (int)sizeof(repl));
TRY(snprintf(repl, sizeof(repl), "\\u%04x", (unsigned)*c) <
(int)sizeof(repl));
TRY(strbuf_push(buf, repl));
c++;
continue;
}

char repl[] = "_";
repl[0] = *c;
repl[0] = (char)*c;
TRY(strbuf_push(buf, repl));
c++;
}
TRY(strbuf_push(buf, "\""));

Expand Down
44 changes: 40 additions & 4 deletions src/cjson/cYAML_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ bool run_test(const char *name, const char *json, const char *wanted) {
}

int main(int argc, char *argv[]) {
run_test("top-level object",
bool ok = true;

ok &= run_test("top-level object",

"{ "
" \"rom\": {"
Expand Down Expand Up @@ -85,7 +87,7 @@ int main(int argc, char *argv[]) {
"- item4\n"
);

run_test("top-level list",
ok &= run_test("top-level list",

"["
" \"item1\","
Expand Down Expand Up @@ -119,7 +121,7 @@ int main(int argc, char *argv[]) {
"- item4\n"
);

run_test("empty objects",
ok &= run_test("empty objects",

"{ "
" \"object\": {},"
Expand All @@ -133,5 +135,39 @@ int main(int argc, char *argv[]) {
"string: \"\"\n"
);

return 0;
/* Non-ASCII used to take down the whole print, not just one string:
* `char` is signed, so every UTF-8 byte tested as < 32, fell into the
* \u expansion and overran its scratch buffer, and cYAML_Print returned
* NULL. ipctool then emitted nothing at all in its default output mode.
* Sensor names and U-Boot environments do carry non-ASCII, so this was
* reachable in ordinary use. UTF-8 is valid YAML and passes through. */
ok &= run_test("utf-8 passes through",

"{ \"note\": \"em dash \\u2014 here\" }",

"---\n"
"note: em dash \xe2\x80\x94 here\n"
);

/* Bytes that are not valid UTF-8 reach cYAML from raw flash -- U-Boot
* environments are passed through untranscoded -- and must not be emitted
* raw, or one bad byte in one value stops the whole document parsing. */
ok &= run_test("invalid utf-8 is escaped, not passed through",

"{ \"note\": \"latin1 \xe9 here\" }",

"---\n"
"note: \"latin1 \\u00e9 here\"\n"
);

/* Control characters still get expanded. */
ok &= run_test("control characters are escaped",

"{ \"note\": \"bell \\u0007 here\" }",

"---\n"
"note: \"bell \\u0007 here\"\n"
);

return ok ? 0 : 1;
}
Loading