From 7b15ce1b3bbd788c65454da0eed06e9c24bbad48 Mon Sep 17 00:00:00 2001 From: Nils Goroll Date: Mon, 30 Sep 2024 10:18:20 +0200 Subject: [PATCH] vmod_debug: add debug.chksha256 / debug.chkcrc32 VDP to check body integrity ... from within varnish, which does not allow to check for issues in the transport, but is useful for validating storage and any previous VDPs in the filter list. crc32 has been added as an option with higher performance, because the algorithm already exists in-tree. --- bin/varnishtest/tests/m00059.vtc | 81 +++++++++ vmod/Makefile.am | 1 + vmod/vmod_debug.c | 292 +++++++++++++++++++++++++++++++ vmod/vmod_debug.vcc | 30 ++++ 4 files changed, 404 insertions(+) create mode 100644 bin/varnishtest/tests/m00059.vtc diff --git a/bin/varnishtest/tests/m00059.vtc b/bin/varnishtest/tests/m00059.vtc new file mode 100644 index 00000000000..596bf87c687 --- /dev/null +++ b/bin/varnishtest/tests/m00059.vtc @@ -0,0 +1,81 @@ +varnishtest "VMOD debug.chksha256" + +server s1 { + rxreq + expect req.url == "/ok" + txresp \ + -hdr "sha256: 9cbca99698fee7cefd93bc6db1c53226fdecae730197fd793a54e170a30af045" \ + -hdr "crc32: 3177021206" \ + -hdr "Transfer-Encoding: chunked" -nolen + chunked "Ponto Facto, " + delay 1 + chunked "Caesar Transit!" + chunkedlen 0 + + rxreq + expect req.url == "/wrong" + txresp \ + -hdr "sha256: 9cbca99698fee7cefd93bc6db1c53226fdecae730197fd793a54e170a30af045" \ + -hdr "crc32: 3177021206" \ + -body "" +} -start + +varnish v1 \ + -arg "-p feature=+no_coredump" \ + -vcl+backend { + import debug; + import blob; + import std; + + sub vcl_deliver { + if (req.http.panic) { + debug.chksha256(blob.decode(HEX, + encoded=resp.http.sha256), panic); + debug.chkcrc32(std.integer(resp.http.crc32), panic); + } else { + debug.chksha256(blob.decode(HEX, + encoded=resp.http.sha256), log); + debug.chkcrc32(std.integer(resp.http.crc32), log); + } + set resp.filters += " debug.chksha256 debug.chkcrc32"; + } +} -start + +logexpect l1 -v v1 -g vxid -q "vxid == 1001" { + fail add * Debug "checksum mismatch" + expect * 1001 Begin + expect * = End + fail clear +} -start + +logexpect l2 -v v1 -g vxid -q "vxid == 1003" { + fail add * End + expect * 1003 Begin + expect * = Debug "^sha256 checksum mismatch" + expect 0 = Debug "^got: 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + expect 0 = Debug "^exp: 0x9cbca99698fee7cefd93bc6db1c53226fdecae730197fd793a54e170a30af045" + fail clear +} -start + +client c1 { + txreq -url "/ok" + rxresp + txreq -url "/wrong" + rxresp +} -run + +varnish v1 -vsl_catchup + +logexpect l1 -wait +logexpect l2 -wait + +client c1 { + txreq -url "/wrong" -hdr "panic: yes" + rxresp +} -run + +delay 3 + +varnish v1 -cliexpect "body checksum" "panic.show" +varnish v1 -cliok "panic.clear" +varnish v1 -expectexit 0x40 diff --git a/vmod/Makefile.am b/vmod/Makefile.am index ad3d4b95a4d..1d2df6317ee 100644 --- a/vmod/Makefile.am +++ b/vmod/Makefile.am @@ -45,6 +45,7 @@ include $(srcdir)/automake_boilerplate_vtc.am VSC_SRC = VSC_debug.vsc libvmod_debug_la_SOURCES += $(VSC_SRC) +libvmod_debug_la_CFLAGS += -I$(top_srcdir)/lib/libvgz BUILT_SOURCES = $(VSC_GEN) diff --git a/vmod/vmod_debug.c b/vmod/vmod_debug.c index 33c1bf45eae..1ced99c2354 100644 --- a/vmod/vmod_debug.c +++ b/vmod/vmod_debug.c @@ -40,6 +40,8 @@ #include "cache/cache_filter.h" #include "vsa.h" +#include "vgz.h" +#include "vsha256.h" #include "vss.h" #include "vtcp.h" #include "vtim.h" @@ -362,6 +364,292 @@ static const struct vdp xyzzy_vdp_slow = { .bytes = xyzzy_vdp_slow_bytes }; +/* + * checksum VDP: + * test that the stream of bytes has a certain checksum and either log + * or panic + * + * The sha256 and crc32 variants are basically identical, but the amount of + * code does not justify generalizing. (slink) + */ + +enum vdp_chk_mode_e { + VDP_CHK_INVAL = 0, + VDP_CHK_LOG, + VDP_CHK_PANIC, + VDP_CHK_PANIC_UNLESS_ERROR +}; + +struct vdp_chksha256_cfg_s { + unsigned magic; +#define VDP_CHKSHA256_CFG_MAGIC 0x624f5b32 + enum vdp_chk_mode_e mode; + unsigned char expected[VSHA256_DIGEST_LENGTH]; +}; + +struct vdp_chkcrc32_cfg_s { + unsigned magic; +#define VDP_CHKCRC32_CFG_MAGIC 0x5a7a835c + enum vdp_chk_mode_e mode; + uint32_t expected; +}; + +struct vdp_chksha256_s { + unsigned magic; +#define VDP_CHKSHA256_MAGIC 0x6856e913 + unsigned called; + size_t bytes; + struct VSHA256Context cx[1]; + struct vdp_chksha256_cfg_s *cfg; +}; + +struct vdp_chkcrc32_s { + unsigned magic; +#define VDP_CHKCRC32_MAGIC 0x15c03d3c + unsigned called; + size_t bytes; + uint32_t crc; + struct vdp_chkcrc32_cfg_s *cfg; +}; + +; + +const void * const chksha256_priv_id = &chksha256_priv_id; +const void * const chkcrc32_priv_id = &chkcrc32_priv_id; + +static int v_matchproto_(vdp_init_f) +xyzzy_chksha256_init(VRT_CTX, struct vdp_ctx *vdc, void **priv) +{ + struct vdp_chksha256_s *vdps; + struct vmod_priv *p; + + CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); + CHECK_OBJ_NOTNULL(vdc, VDP_CTX_MAGIC); + CHECK_OBJ_ORNULL(vdc->oc, OBJCORE_MAGIC); + CHECK_OBJ_NOTNULL(vdc->hp, HTTP_MAGIC); + AN(vdc->clen); + AN(priv); + + WS_TASK_ALLOC_OBJ(ctx, vdps, VDP_CHKSHA256_MAGIC); + if (vdps == NULL) + return (-1); + VSHA256_Init(vdps->cx); + + p = VRT_priv_task_get(ctx, chksha256_priv_id); + if (p == NULL) + return (-1); + + assert(p->len == sizeof(struct vdp_chksha256_cfg_s)); + CAST_OBJ_NOTNULL(vdps->cfg, p->priv, VDP_CHKSHA256_CFG_MAGIC); + *priv = vdps; + + return (0); +} + +static int v_matchproto_(vdp_init_f) +xyzzy_chkcrc32_init(VRT_CTX, struct vdp_ctx *vdc, void **priv) +{ + struct vdp_chkcrc32_s *vdps; + struct vmod_priv *p; + + CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); + CHECK_OBJ_NOTNULL(vdc, VDP_CTX_MAGIC); + CHECK_OBJ_ORNULL(vdc->oc, OBJCORE_MAGIC); + CHECK_OBJ_NOTNULL(vdc->hp, HTTP_MAGIC); + AN(vdc->clen); + AN(priv); + + WS_TASK_ALLOC_OBJ(ctx, vdps, VDP_CHKCRC32_MAGIC); + if (vdps == NULL) + return (-1); + vdps->crc = crc32(0L, Z_NULL, 0); + + p = VRT_priv_task_get(ctx, chkcrc32_priv_id); + if (p == NULL) + return (-1); + + assert(p->len == sizeof(struct vdp_chkcrc32_cfg_s)); + CAST_OBJ_NOTNULL(vdps->cfg, p->priv, VDP_CHKCRC32_CFG_MAGIC); + *priv = vdps; + + return (0); +} + +static int v_matchproto_(vdp_bytes_f) +xyzzy_chksha256_bytes(struct vdp_ctx *vdc, enum vdp_action act, void **priv, + const void *ptr, ssize_t len) +{ + struct vdp_chksha256_s *vdps; + + CAST_OBJ_NOTNULL(vdps, *priv, VDP_CHKSHA256_MAGIC); + VSHA256_Update(vdps->cx, ptr, len); + vdps->called++; + vdps->bytes += len; + return (VDP_bytes(vdc, act, ptr, len)); +} + +static int v_matchproto_(vdp_bytes_f) +xyzzy_chkcrc32_bytes(struct vdp_ctx *vdc, enum vdp_action act, void **priv, + const void *ptr, ssize_t len) +{ + struct vdp_chkcrc32_s *vdps; + + CAST_OBJ_NOTNULL(vdps, *priv, VDP_CHKCRC32_MAGIC); + if (len > 0) + vdps->crc = crc32(vdps->crc, ptr, len); + vdps->called++; + vdps->bytes += len; + return (VDP_bytes(vdc, act, ptr, len)); +} + +static int v_matchproto_(vdp_fini_f) +xyzzy_chksha256_fini(struct vdp_ctx *vdc, void **priv) +{ + unsigned char digest[VSHA256_DIGEST_LENGTH]; + enum vdp_chk_mode_e mode; + struct vdp_chksha256_s *vdps; + struct vsb *vsb; + int r; + + (void) vdc; + AN(priv); + if (*priv == NULL) + return (0); + CAST_OBJ_NOTNULL(vdps, *priv, VDP_CHKSHA256_MAGIC); + *priv = NULL; + + VSHA256_Final(digest, vdps->cx); + r = memcmp(digest, vdps->cfg->expected, sizeof digest); + if (r == 0) + return (0); + + mode = vdps->cfg->mode; + if (mode == VDP_CHK_PANIC_UNLESS_ERROR) + mode = (vdps->called == 0 || vdc->retval != 0) ? VDP_CHK_LOG : VDP_CHK_PANIC; + + if (mode == VDP_CHK_LOG) { + VSLb(vdc->vsl, SLT_Debug, "sha256 checksum mismatch"); + + vsb = VSB_new_auto(); + VSB_quote(vsb, digest, sizeof digest, VSB_QUOTE_HEX); + AZ(VSB_finish(vsb)); + VSLb(vdc->vsl, SLT_Debug, "got: %s", VSB_data(vsb)); + + VSB_clear(vsb); + VSB_quote(vsb, vdps->cfg->expected, sizeof digest, VSB_QUOTE_HEX); + AZ(VSB_finish(vsb)); + VSLb(vdc->vsl, SLT_Debug, "exp: %s", VSB_data(vsb)); + VSB_destroy(&vsb); + } + else if (mode == VDP_CHK_PANIC) + WRONG("body checksum"); + else + WRONG("mode"); + + return (0); +} + +static int v_matchproto_(vdp_fini_f) +xyzzy_chkcrc32_fini(struct vdp_ctx *vdc, void **priv) +{ + enum vdp_chk_mode_e mode; + struct vdp_chkcrc32_s *vdps; + + (void) vdc; + AN(priv); + if (*priv == NULL) + return (0); + CAST_OBJ_NOTNULL(vdps, *priv, VDP_CHKCRC32_MAGIC); + *priv = NULL; + + if (vdps->crc == vdps->cfg->expected) + return (0); + + mode = vdps->cfg->mode; + if (mode == VDP_CHK_PANIC_UNLESS_ERROR) + mode = (vdps->called == 0 || vdc->retval != 0) ? VDP_CHK_LOG : VDP_CHK_PANIC; + + if (mode == VDP_CHK_LOG) { + VSLb(vdc->vsl, SLT_Debug, "crc32 checksum mismatch"); + VSLb(vdc->vsl, SLT_Debug, "got: %08x", vdps->crc); + VSLb(vdc->vsl, SLT_Debug, "exp: %08x", vdps->cfg->expected); + } + else if (mode == VDP_CHK_PANIC) + WRONG("body checksum"); + else + WRONG("mode"); + + return (0); +} + +static const struct vdp xyzzy_vdp_chksha256 = { + .name = "debug.chksha256", + .init = xyzzy_chksha256_init, + .bytes = xyzzy_chksha256_bytes, + .fini = xyzzy_chksha256_fini, +}; + +static const struct vdp xyzzy_vdp_chkcrc32 = { + .name = "debug.chkcrc32", + .init = xyzzy_chkcrc32_init, + .bytes = xyzzy_chkcrc32_bytes, + .fini = xyzzy_chkcrc32_fini, +}; + +#define chkcfg(ws, cfg, magic, id, mode_e) do { \ + struct vmod_priv *p = VRT_priv_task(ctx, id); \ + \ + XXXAN(p); \ + if (p->priv == NULL) { \ + p->priv = WS_Alloc(ws, sizeof *cfg); \ + p->len = sizeof *cfg; \ + } \ + cfg = p->priv; \ + INIT_OBJ(cfg, magic); \ + if (mode_e == VENUM(log)) \ + cfg->mode = VDP_CHK_LOG; \ + else if (mode_e == VENUM(panic)) \ + cfg->mode = VDP_CHK_PANIC; \ + else if (mode_e == VENUM(panic_unless_error)) \ + cfg->mode = VDP_CHK_PANIC_UNLESS_ERROR; \ + else \ + WRONG("mode_e"); \ +} while(0) + +VCL_VOID v_matchproto_(td_xyzzy_debug_chksha256) +xyzzy_chksha256(VRT_CTX, VCL_BLOB blob, VCL_ENUM mode_e) +{ + struct vdp_chksha256_cfg_s *cfg; + size_t l; + + CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); + AN(blob); + XXXAN(blob->blob); + XXXAN(blob->len); + + chkcfg(ctx->ws, cfg, VDP_CHKSHA256_CFG_MAGIC, chksha256_priv_id, mode_e); + + l = blob->len; + if (l > sizeof cfg->expected) + l = sizeof cfg->expected; + memcpy(cfg->expected, blob->blob, l); + +} + +VCL_VOID v_matchproto_(td_xyzzy_debug_chkcrc32) +xyzzy_chkcrc32(VRT_CTX, VCL_INT expected, VCL_ENUM mode_e) +{ + struct vdp_chkcrc32_cfg_s *cfg; + + CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); + + chkcfg(ctx->ws, cfg, VDP_CHKCRC32_CFG_MAGIC, chkcrc32_priv_id, mode_e); + + if (expected < 0) + expected = 0; + cfg->expected = (uintmax_t)expected % UINT32_MAX; +} + /**********************************************************************/ VCL_STRING v_matchproto_(td_debug_author) @@ -634,6 +922,8 @@ event_load(VRT_CTX, struct vmod_priv *priv) AZ(VRT_AddFilter(ctx, NULL, &xyzzy_vdp_pedantic)); AZ(VRT_AddFilter(ctx, NULL, &xyzzy_vdp_chunked)); AZ(VRT_AddFilter(ctx, &xyzzy_vfp_slow, &xyzzy_vdp_slow)); + AZ(VRT_AddFilter(ctx, NULL, &xyzzy_vdp_chksha256)); + AZ(VRT_AddFilter(ctx, NULL, &xyzzy_vdp_chkcrc32)); return (0); } @@ -799,6 +1089,8 @@ event_discard(VRT_CTX, void *priv) VRT_RemoveFilter(ctx, &xyzzy_vfp_rot13, &xyzzy_vdp_rot13); VRT_RemoveFilter(ctx, NULL, &xyzzy_vdp_pedantic); VRT_RemoveFilter(ctx, NULL, &xyzzy_vdp_chunked); + VRT_RemoveFilter(ctx, NULL, &xyzzy_vdp_chksha256); + VRT_RemoveFilter(ctx, NULL, &xyzzy_vdp_chkcrc32); if (--loads) return (0); diff --git a/vmod/vmod_debug.vcc b/vmod/vmod_debug.vcc index 0299c19cb49..e3f80b0ccd5 100644 --- a/vmod/vmod_debug.vcc +++ b/vmod/vmod_debug.vcc @@ -410,6 +410,36 @@ resolved sockets are retuned in a comma delimited string. If fail_port is specified, the resolution callback will fail for that port, and the reason will be appended to the return value. +$Function VOID chksha256(BLOB expected, ENUM {log, panic, panic_unless_error} mode) + +Configure the expected sha256 checksum and failure mode for the debug.chksha256 +VDP. This function does not push the VDP. + +The *expected* blob should be 32 bytes in length. If not, it will either be +truncated or padded with zeros. + +With *mode* ``log``, the VDP emits ``Debug`` VSL like the following for a +checksum mismatch:: + + Debug c checksum mismatch + Debug c got: 0xe3b0c44298fc1c149afbf4c8996fb924... + Debug c exp: 0x9cbca99698fee7cefd93bc6db1c53226... + +With *mode* ``panic``, the VDP triggers a ``WRONG("body checksum")`` for a +mismatch. The ``panic_unless_error`` *mode* does so only if the filter chain was +otherwise closed without error. This is useful, for example, to not trigger a +panic when the client closes the connection. + +$Function VOID chkcrc32(INT expected, ENUM {log, panic, panic_unless_error} mode) + +Configure the expected crc32 checksum and failure mode for the debug.chkcrc32 +VDP. This function does not push the VDP. + +*expected* needs to be in the range ``0 .. UINT32_MAX``. A negative value will +be hanged to zero. Any larger value will be taken modulo UINT32_MAX. + +The *mode* argument behaves as for `debug.chksha256()`_. + DEPRECATED ==========