commit - bd4ce114c04c569958acb4d8c7ecb00465221fb3
commit + 79f05bf96cd7e01f3482670fd236c624bba177e0
blob - 1090a5bf0d5dc1f8c9e76e79baba43a936f5db9a
blob + dfeb75234cd47546b69f831bdd53ad9733c02a09
--- regress/artifact_result/artifact_result_test.c
+++ regress/artifact_result/artifact_result_test.c
static const char good_id[] = "0123456789abcdef0123456789abcdef";
+static void streamed_matches_contiguous(const void *, size_t, size_t);
+
static int
field_count(const struct json *j, int obj, const char *name)
{
return (json_get_str(j, json_obj_get(j, root, name), len));
}
+static int
+base64_value(u_char c)
+{
+ if (c >= 'A' && c <= 'Z')
+ return (c - 'A');
+ if (c >= 'a' && c <= 'z')
+ return (c - 'a' + 26);
+ if (c >= '0' && c <= '9')
+ return (c - '0' + 52);
+ if (c == '+')
+ return (62);
+ if (c == '/')
+ return (63);
+ return (-1);
+}
+
+static int
+base64_decode(struct buf *out, const char *src, size_t len)
+{
+ uint32_t v;
+ int a, b, c, d;
+ size_t i;
+
+ buf_reset(out);
+ if (len % 4 != 0)
+ return (-1);
+ for (i = 0; i < len; i += 4) {
+ a = base64_value((u_char)src[i]);
+ b = base64_value((u_char)src[i + 1]);
+ c = src[i + 2] == '=' ? -2 :
+ base64_value((u_char)src[i + 2]);
+ d = src[i + 3] == '=' ? -2 :
+ base64_value((u_char)src[i + 3]);
+ if (a < 0 || b < 0 || c == -1 || d == -1 ||
+ (c == -2 && d != -2) ||
+ ((c == -2 || d == -2) && i + 4 != len))
+ return (-1);
+ v = ((uint32_t)a << 18) | ((uint32_t)b << 12);
+ if (c >= 0)
+ v |= (uint32_t)c << 6;
+ if (d >= 0)
+ v |= (uint32_t)d;
+ buf_addc(out, (u_char)(v >> 16));
+ if (c >= 0)
+ buf_addc(out, (u_char)(v >> 8));
+ if (d >= 0)
+ buf_addc(out, (u_char)v);
+ }
+ return (0);
+}
+
+static size_t
+encoded_field_len(const struct buf *out, const char *name)
+{
+ char pattern[64];
+ const u_char *p = out->data;
+ size_t i, j, n, start;
+ int written;
+
+ written = snprintf(pattern, sizeof(pattern), "\"%s\":\"", name);
+ if (written < 0 || (size_t)written >= sizeof(pattern))
+ return (SIZE_MAX);
+ n = (size_t)written;
+ for (i = 0; i + n <= out->len; i++) {
+ if (memcmp(p + i, pattern, n) != 0)
+ continue;
+ start = i + n;
+ for (j = start; j < out->len;) {
+ if (p[j] == '"')
+ return (j - start);
+ if (p[j] == '\\') {
+ if (j + 1 >= out->len)
+ return (SIZE_MAX);
+ j += 2;
+ } else
+ j++;
+ }
+ return (SIZE_MAX);
+ }
+ return (SIZE_MAX);
+}
+
+static int
+preview_bytes(const struct json *j, int root, const char *field,
+ const char *encoding, struct buf *decoded)
+{
+ char *value;
+ size_t len;
+ int rc = 0;
+
+ value = string_field(j, root, field, &len);
+ if (value == NULL)
+ return (-1);
+ buf_reset(decoded);
+ if (strcmp(encoding, "base64") == 0)
+ rc = base64_decode(decoded, value, len);
+ else if (strcmp(encoding, "utf-8") == 0)
+ buf_add(decoded, value, len);
+ else
+ rc = -1;
+ free(value);
+ return (rc);
+}
+
static void
+check_preview_result(const struct buf *out, const u_char *raw, size_t raw_len,
+ size_t head_raw_len, size_t tail_raw_len, const char *want_encoding,
+ int abbreviated)
+{
+ struct json j;
+ struct buf head, tail;
+ char *encoding, *id;
+ size_t elen, head_encoded, idlen, tail_encoded;
+ int root;
+
+ CHECK(out->len <= ARTIFACT_RESULT_MAX);
+ CHECK(json_parse(&j, out->data, out->len, 0) == 0);
+ root = json_root(&j);
+ CHECK(json_get_bool(&j, json_obj_get(&j, root,
+ "preview_abbreviated")) == abbreviated);
+ CHECK(field_count(&j, root, "id") == abbreviated);
+ if (abbreviated) {
+ id = string_field(&j, root, "id", &idlen);
+ CHECK(id != NULL && idlen == sizeof(good_id) - 1 &&
+ memcmp(id, good_id, idlen) == 0);
+ free(id);
+ } else
+ CHECK(json_obj_get(&j, root, "id") == -1);
+ encoding = string_field(&j, root, "preview_encoding", &elen);
+ CHECK(encoding != NULL && elen == strlen(want_encoding) &&
+ memcmp(encoding, want_encoding, elen) == 0);
+ head_encoded = encoded_field_len(out, "preview_head");
+ tail_encoded = encoded_field_len(out, "preview_tail");
+ CHECK(head_encoded <= ARTIFACT_PREVIEW_HEAD_ENCODED_MAX);
+ CHECK(tail_encoded <= ARTIFACT_PREVIEW_TAIL_ENCODED_MAX);
+ CHECK(head_encoded != SIZE_MAX && tail_encoded != SIZE_MAX &&
+ out->len >= head_encoded + tail_encoded &&
+ out->len - head_encoded - tail_encoded <=
+ ARTIFACT_RESULT_META_RESERVE);
+ buf_init(&head);
+ buf_init(&tail);
+ CHECK(encoding != NULL &&
+ preview_bytes(&j, root, "preview_head", want_encoding, &head) == 0);
+ CHECK(encoding != NULL &&
+ preview_bytes(&j, root, "preview_tail", want_encoding, &tail) == 0);
+ CHECK(head.len == head_raw_len && tail.len == tail_raw_len);
+ CHECK(head.len <= raw_len && tail.len <= raw_len - head.len &&
+ memcmp(head.data, raw, head.len) == 0 &&
+ memcmp(tail.data, raw + raw_len - tail.len, tail.len) == 0);
+ if (!abbreviated)
+ CHECK(head.len + tail.len == raw_len);
+ buf_free(&tail);
+ buf_free(&head);
+ free(encoding);
+ json_done(&j);
+}
+
+static void
test_inline_and_forgery(void)
{
static const char *const keys[] = {
{
static const struct {
const char *name;
- u_char byte;
+ u_char head_byte;
+ u_char marker_byte;
+ u_char tail_byte;
const char *encoding;
- size_t encoded_step;
+ size_t head_raw_max;
+ size_t tail_raw_max;
} shapes[] = {
- { "printable", 'x', "utf-8", 1 },
- { "escape-heavy", '\1', "utf-8", 6 },
- { "NUL", '\0', "utf-8", 6 },
- { "invalid UTF-8/base64", 0xff, "base64", 4 }
+ { "printable", 'h', 'm', 't', "utf-8",
+ ARTIFACT_PREVIEW_HEAD_ENCODED_MAX,
+ ARTIFACT_PREVIEW_TAIL_ENCODED_MAX },
+ { "escape-heavy", '\1', '\2', '\3', "utf-8",
+ ARTIFACT_PREVIEW_HEAD_ENCODED_MAX / 6,
+ ARTIFACT_PREVIEW_TAIL_ENCODED_MAX / 6 },
+ { "NUL", '\0', '\1', '\37', "utf-8",
+ ARTIFACT_PREVIEW_HEAD_ENCODED_MAX / 6,
+ ARTIFACT_PREVIEW_TAIL_ENCODED_MAX / 6 },
+ { "invalid UTF-8/base64", 0xfd, 0xff, 0xfe, "base64",
+ ARTIFACT_PREVIEW_HEAD_ENCODED_MAX / 4 * 3,
+ ARTIFACT_PREVIEW_TAIL_ENCODED_MAX / 4 * 3 }
};
struct artifact_result in;
- struct json j;
struct buf out;
- char *encoding;
u_char *raw;
- size_t elen, hi, i, lo, mid;
- int root;
+ size_t i, max;
raw = malloc(ARTIFACT_RESULT_MAX + 1);
CHECK(raw != NULL);
return;
buf_init(&out);
for (i = 0; i < sizeof(shapes) / sizeof(shapes[0]); i++) {
- memset(raw, shapes[i].byte, ARTIFACT_RESULT_MAX + 1);
- lo = 0;
- hi = ARTIFACT_RESULT_MAX + 1;
- while (lo + 1 < hi) {
- mid = lo + (hi - lo) / 2;
- if (artifact_result_inline_fits(raw, mid))
- lo = mid;
- else
- hi = mid;
- }
- CHECK(lo != 0 && artifact_result_inline_fits(raw, lo));
- CHECK(!artifact_result_inline_fits(raw, lo + 1));
+ max = shapes[i].head_raw_max + shapes[i].tail_raw_max;
+ memset(raw, shapes[i].head_byte, shapes[i].head_raw_max);
+ memset(raw + shapes[i].head_raw_max, shapes[i].tail_byte,
+ shapes[i].tail_raw_max);
+ CHECK(artifact_result_inline_fits(raw, max));
memset(&in, 0, sizeof(in));
in.raw = raw;
- in.raw_len = lo;
- in.observed_bytes = lo;
- in.retained_bytes = lo;
+ in.raw_len = max;
+ in.observed_bytes = max;
+ in.retained_bytes = max;
in.termination = ARTIFACT_TERM_EXIT;
CHECK(artifact_result_build(&out, &in) == 0 &&
- out.len <= ARTIFACT_RESULT_MAX &&
- ARTIFACT_RESULT_MAX - out.len < shapes[i].encoded_step);
- CHECK(json_parse(&j, out.data, out.len, 0) == 0);
- root = json_root(&j);
- CHECK(json_get_bool(&j, json_obj_get(&j, root,
- "preview_abbreviated")) == 0);
- encoding = string_field(&j, root, "preview_encoding", &elen);
- CHECK(encoding != NULL && elen == strlen(shapes[i].encoding) &&
- memcmp(encoding, shapes[i].encoding, elen) == 0);
- free(encoding);
- json_done(&j);
+ out.len <= ARTIFACT_RESULT_MAX);
+ check_preview_result(&out, raw, max, shapes[i].head_raw_max,
+ shapes[i].tail_raw_max, shapes[i].encoding, 0);
+ streamed_matches_contiguous(raw, max, 7919);
- in.raw_len = lo + 1;
- in.observed_bytes = lo + 1;
- in.retained_bytes = lo + 1;
+ memmove(raw + shapes[i].head_raw_max + 1,
+ raw + shapes[i].head_raw_max, shapes[i].tail_raw_max);
+ raw[shapes[i].head_raw_max] = shapes[i].marker_byte;
+ CHECK(!artifact_result_inline_fits(raw, max + 1));
+ in.raw_len = max + 1;
+ in.observed_bytes = max + 1;
+ in.retained_bytes = max + 1;
in.id = good_id;
CHECK(artifact_result_build(&out, &in) == 0 &&
out.len <= ARTIFACT_RESULT_MAX);
- CHECK(json_parse(&j, out.data, out.len, 0) == 0);
- root = json_root(&j);
- CHECK(json_get_bool(&j, json_obj_get(&j, root,
- "preview_abbreviated")) == 1);
- json_done(&j);
+ check_preview_result(&out, raw, max + 1,
+ shapes[i].head_raw_max, shapes[i].tail_raw_max,
+ shapes[i].encoding, 1);
+ streamed_matches_contiguous(raw, max + 1, 16381);
(void)shapes[i].name;
}
buf_free(&out);
}
static void
+test_multibyte_inline_boundary(void)
+{
+ static const u_char euro[] = { 0xe2, 0x82, 0xac };
+ static const u_char heart[] = { 0xe2, 0x99, 0xa5 };
+ static const u_char snowman[] = { 0xe2, 0x98, 0x83 };
+ struct artifact_result in;
+ struct buf out;
+ u_char *raw;
+ size_t head_len, i, max, tail_len;
+
+ head_len = ARTIFACT_PREVIEW_HEAD_ENCODED_MAX / sizeof(euro) *
+ sizeof(euro);
+ tail_len = ARTIFACT_PREVIEW_TAIL_ENCODED_MAX / sizeof(snowman) *
+ sizeof(snowman);
+ max = head_len + tail_len;
+ raw = malloc(max + sizeof(heart));
+ CHECK(raw != NULL);
+ if (raw == NULL)
+ return;
+ for (i = 0; i < head_len; i += sizeof(euro))
+ memcpy(raw + i, euro, sizeof(euro));
+ for (i = 0; i < tail_len; i += sizeof(snowman))
+ memcpy(raw + head_len + i, snowman, sizeof(snowman));
+ CHECK(artifact_result_inline_fits(raw, max));
+
+ memset(&in, 0, sizeof(in));
+ in.raw = raw;
+ in.raw_len = max;
+ in.observed_bytes = max;
+ in.retained_bytes = max;
+ in.termination = ARTIFACT_TERM_EXIT;
+ buf_init(&out);
+ CHECK(artifact_result_build(&out, &in) == 0 &&
+ out.len <= ARTIFACT_RESULT_MAX);
+ check_preview_result(&out, raw, max, head_len, tail_len, "utf-8", 0);
+ streamed_matches_contiguous(raw, max, 4093);
+
+ /* A complete rune between the exact spans is omitted without splitting
+ * either multibyte boundary. */
+ memmove(raw + head_len + sizeof(heart), raw + head_len, tail_len);
+ memcpy(raw + head_len, heart, sizeof(heart));
+ CHECK(!artifact_result_inline_fits(raw, max + sizeof(heart)));
+ in.raw_len = max + sizeof(heart);
+ in.observed_bytes = in.raw_len;
+ in.retained_bytes = in.raw_len;
+ in.id = good_id;
+ CHECK(artifact_result_build(&out, &in) == 0);
+ check_preview_result(&out, raw, in.raw_len, head_len, tail_len,
+ "utf-8", 1);
+ streamed_matches_contiguous(raw, in.raw_len, 8191);
+
+ /* A partial rune makes the complete stream binary; base64 still selects
+ * the exact earliest and latest raw bytes within its own budgets. */
+ raw[head_len] = heart[0];
+ memmove(raw + head_len + 1, raw + head_len + sizeof(heart), tail_len);
+ in.raw_len = max + 1;
+ in.observed_bytes = in.raw_len;
+ in.retained_bytes = in.raw_len;
+ CHECK(!artifact_result_inline_fits(raw, in.raw_len));
+ CHECK(artifact_result_build(&out, &in) == 0);
+ check_preview_result(&out, raw, in.raw_len,
+ ARTIFACT_PREVIEW_HEAD_ENCODED_MAX / 4 * 3,
+ ARTIFACT_PREVIEW_TAIL_ENCODED_MAX / 4 * 3, "base64", 1);
+ streamed_matches_contiguous(raw, in.raw_len, 12289);
+ buf_free(&out);
+ free(raw);
+}
+
+static void
test_error_metadata_is_not_spoofable(void)
{
static const char detail[] = "spawn failed before capture";
{
struct artifact_result in;
struct buf raw, error, out;
+ size_t head_encoded, tail_encoded;
buf_init(&raw);
buf_init(&error);
in.error_len = error.len;
CHECK(artifact_result_build(&out, &in) == 0);
CHECK(out.len <= ARTIFACT_RESULT_MAX);
+ head_encoded = encoded_field_len(&out, "preview_head");
+ tail_encoded = encoded_field_len(&out, "preview_tail");
+ CHECK(head_encoded <= ARTIFACT_PREVIEW_HEAD_ENCODED_MAX &&
+ tail_encoded <= ARTIFACT_PREVIEW_TAIL_ENCODED_MAX &&
+ out.len >= head_encoded + tail_encoded &&
+ out.len - head_encoded - tail_encoded <=
+ ARTIFACT_RESULT_META_RESERVE);
buf_addc(&error, '\1');
in.error_len = error.len;
CHECK(artifact_result_build(&out, &in) == -1);
test_binary_base64();
test_boundary_selection();
test_exact_inline_boundaries();
+ test_multibyte_inline_boundary();
test_error_metadata_is_not_spoofable();
test_json_integer_range();
test_empty_streaming_capture_is_inline();
blob - de0a9757de6e43f56feae09c70cb629d4c2e5c52
blob + 010b0e3a50f631a8baadbb405958703a312b986d
--- src/fugu/artifact_result.c
+++ src/fugu/artifact_result.c
}
static void
+preview_spans(const u_char *raw, size_t len, int utf8,
+ struct preview_span *head, struct preview_span *tail)
+{
+ size_t maxraw;
+
+ if (utf8) {
+ utf8_head(raw, len, ARTIFACT_PREVIEW_HEAD_ENCODED_MAX, head);
+ utf8_tail(raw, len, ARTIFACT_PREVIEW_TAIL_ENCODED_MAX, tail);
+ if (head->len + tail->len > len) {
+ tail->data = raw + head->len;
+ tail->len = len - head->len;
+ }
+ return;
+ }
+ maxraw = base64_raw_max(ARTIFACT_PREVIEW_HEAD_ENCODED_MAX);
+ head->data = raw;
+ head->len = len < maxraw ? len : maxraw;
+ maxraw = base64_raw_max(ARTIFACT_PREVIEW_TAIL_ENCODED_MAX);
+ tail->len = len - head->len;
+ if (tail->len > maxraw)
+ tail->len = maxraw;
+ tail->data = raw + len - tail->len;
+}
+
+static int
+preview_complete(const u_char *raw, size_t len,
+ const struct preview_span *head, const struct preview_span *tail)
+{
+ return (head->data == raw && tail->data == raw + head->len &&
+ head->len <= len && tail->len == len - head->len);
+}
+
+static void
base64_add(struct buf *out, const u_char *p, size_t len)
{
static const char table[] =
const u_char *raw = in->raw != NULL ? in->raw : ∅
struct preview_span head, tail;
int utf8, abbreviated;
- size_t maxraw;
if (result_valid(in, 1) == -1)
return (-1);
utf8 = valid_utf8(raw, in->raw_len);
+ preview_spans(raw, in->raw_len, utf8, &head, &tail);
abbreviated = !inline_ok;
- if (!abbreviated) {
- head.data = raw;
- head.len = in->raw_len;
- tail.data = raw + in->raw_len;
- tail.len = 0;
- } else if (utf8) {
- utf8_head(raw, in->raw_len,
- ARTIFACT_PREVIEW_HEAD_ENCODED_MAX, &head);
- utf8_tail(raw, in->raw_len,
- ARTIFACT_PREVIEW_TAIL_ENCODED_MAX, &tail);
- if (head.len + tail.len > in->raw_len) {
- tail.data = raw + head.len;
- tail.len = in->raw_len - head.len;
- }
- } else {
- maxraw = base64_raw_max(ARTIFACT_PREVIEW_HEAD_ENCODED_MAX);
- head.data = raw;
- head.len = in->raw_len < maxraw ? in->raw_len : maxraw;
- maxraw = base64_raw_max(ARTIFACT_PREVIEW_TAIL_ENCODED_MAX);
- tail.len = in->raw_len - head.len;
- if (tail.len > maxraw)
- tail.len = maxraw;
- tail.data = raw + in->raw_len - tail.len;
- }
+ if (!abbreviated &&
+ !preview_complete(raw, in->raw_len, &head, &tail))
+ return (-1);
return (emit_spans(out, in, &head, &tail, utf8, abbreviated));
}