diff --git a/libcontainer/nsenter/escape.c b/libcontainer/nsenter/escape.c new file mode 100644 index 00000000000..eaade26fb2e --- /dev/null +++ b/libcontainer/nsenter/escape.c @@ -0,0 +1,91 @@ +#include + +static char hex(char i) { + if (i >= 0 && i < 10) { + return '0' + i; + } + if (i >= 10 && i < 16) { + return 'a' + i - 10; + } + return '?'; +} + +/* + * Escape the string to be usable as JSON string. + */ +char *escape_json_string(char *str) { + int i, j = 0; + char *out; + + // Avoid malloc by checking first if escaping is required. + // While at it, count how much additional space we need. + // XXX: the counting code need to be in sync with the rest! + for (i = 0; str[i] != '\0'; i++) { + switch (str[i]) { + case '\\': + case '"': + case '\b': + case '\n': + case '\r': + case '\t': + case '\f': + j += 2; + break; + default: + if (str[i] < ' ') { + // \u00xx + j += 6; + } + } + } + if (j == 0) { + // nothing to escape + return str; + } + + out = malloc(i + j); + if (!out) { + exit(1); + } + for (i = j = 0; str[i] != '\0'; i++, j++) { + switch (str[i]) { + case '"': + case '\\': + out[j++] = '\\'; + out[j] = str[i]; + continue; + } + if (str[i] >= ' ') { + out[j] = str[i]; + continue; + } + out[j++] = '\\'; + switch (str[i]) { + case '\b': + out[j] = 'b'; + break; + case '\n': + out[j] = 'n'; + break; + case '\r': + out[j] = 'r'; + break; + case '\t': + out[j] = 't'; + break; + case '\f': + out[j] = 'f'; + break; + default: + out[j++] = 'u'; + out[j++] = '0'; + out[j++] = '0'; + out[j++] = hex(str[i] >> 4); + out[j] = hex(str[i] & 0x0f); + } + } + out[j] = '\0'; + + free(str); + return out; +} diff --git a/libcontainer/nsenter/escape.go b/libcontainer/nsenter/escape.go new file mode 100644 index 00000000000..c8a9860c7cd --- /dev/null +++ b/libcontainer/nsenter/escape.go @@ -0,0 +1,24 @@ +package nsenter + +// This file is part of escape_json_string unit test. It would be a part +// of escape_test.go if Go would allow cgo to be used in _test.go files. + +// #include +// #include "escape.h" +import "C" + +import ( + "testing" + "unsafe" +) + +func testEscapeJsonString(t *testing.T, input, want string) { + in := C.CString(input) + out := C.escape_json_string(in) + got := C.GoString(out) + C.free(unsafe.Pointer(out)) + t.Logf("input: %q, output: %q", input, got) + if got != want { + t.Errorf("Failed on input: %q, want %q, got %q", input, want, got) + } +} diff --git a/libcontainer/nsenter/escape.h b/libcontainer/nsenter/escape.h new file mode 100644 index 00000000000..80d660a4338 --- /dev/null +++ b/libcontainer/nsenter/escape.h @@ -0,0 +1 @@ +extern char *escape_json_string(char *str); diff --git a/libcontainer/nsenter/escape_test.go b/libcontainer/nsenter/escape_test.go new file mode 100644 index 00000000000..499d8a4fb3a --- /dev/null +++ b/libcontainer/nsenter/escape_test.go @@ -0,0 +1,22 @@ +package nsenter + +import "testing" + +func TestEscapeJsonString(t *testing.T) { + testCases := []struct { + input, output string + }{ + {"", ""}, + {"abcdef", "abcdef"}, + {`\\\\\\`, `\\\\\\\\\\\\`}, + {`with"quote`, `with\"quote`}, + {"\n\r\b\t\f\\", `\n\r\b\t\f\\`}, + {"\007", "\\u0007"}, + {"\017 \020 \037", "\\u000f \\u0010 \\u001f"}, + {"\033", "\\u001b"}, + } + + for _, tc := range testCases { + testEscapeJsonString(t, tc.input, tc.output) + } +} diff --git a/libcontainer/nsenter/nsexec.c b/libcontainer/nsenter/nsexec.c index 325045fdd08..e4507ca677e 100644 --- a/libcontainer/nsenter/nsexec.c +++ b/libcontainer/nsenter/nsexec.c @@ -28,6 +28,7 @@ /* Get all of the CLONE_NEW* flags. */ #include "namespace.h" +#include "escape.h" /* Synchronisation values. */ enum sync_t { @@ -153,6 +154,8 @@ static void write_log(const char *level, const char *format, ...) if (ret < 0) goto out; + message = escape_json_string(message); + if (current_stage == STAGE_SETUP) stage = strdup("nsexec"); else