summaryrefslogtreecommitdiff
path: root/src/zipmap.c
diff options
context:
space:
mode:
authorOran Agra <oran@redislabs.com>2020-08-13 16:41:05 +0300
committerOran Agra <oran@redislabs.com>2020-12-06 14:54:34 +0200
commitca1c182567add4092e9cb6ea829e9c5193e8fd55 (patch)
treec4ccd1e235d797066dda7e24bccec9b5473d7981 /src/zipmap.c
parentc4fdf09c0584a3cee32b92f01b7958c72776aedc (diff)
downloadredis-ca1c182567add4092e9cb6ea829e9c5193e8fd55.tar.gz
Sanitize dump payload: ziplist, listpack, zipmap, intset, stream
When loading an encoded payload we will at least do a shallow validation to check that the size that's encoded in the payload matches the size of the allocation. This let's us later use this encoded size to make sure the various offsets inside encoded payload don't reach outside the allocation, if they do, we'll assert/panic, but at least we won't segfault or smear memory. We can also do 'deep' validation which runs on all the records of the encoded payload and validates that they don't contain invalid offsets. This lets us detect corruptions early and reject a RESTORE command rather than accepting it and asserting (crashing) later when accessing that payload via some command. configuration: - adding ACL flag skip-sanitize-payload - adding config sanitize-dump-payload [yes/no/clients] For now, we don't have a good way to ensure MIGRATE in cluster resharding isn't being slowed down by these sanitation, so i'm setting the default value to `no`, but later on it should be set to `clients` by default. changes: - changing rdbReportError not to `exit` in RESTORE command - adding a new stat to be able to later check if cluster MIGRATE isn't being slowed down by sanitation.
Diffstat (limited to 'src/zipmap.c')
-rw-r--r--src/zipmap.c68
1 files changed, 68 insertions, 0 deletions
diff --git a/src/zipmap.c b/src/zipmap.c
index 365c4aea4..bd41fe6a5 100644
--- a/src/zipmap.c
+++ b/src/zipmap.c
@@ -111,6 +111,10 @@ static unsigned int zipmapDecodeLength(unsigned char *p) {
return len;
}
+static unsigned int zipmapGetEncodedLengthSize(unsigned char *p) {
+ return (*p < ZIPMAP_BIGLEN) ? 1: 5;
+}
+
/* Encode the length 'l' writing it in 'p'. If p is NULL it just returns
* the amount of bytes required to encode such a length. */
static unsigned int zipmapEncodeLength(unsigned char *p, unsigned int len) {
@@ -370,6 +374,70 @@ size_t zipmapBlobLen(unsigned char *zm) {
return totlen;
}
+/* Validate the integrity of the data stracture.
+ * when `deep` is 0, only the integrity of the header is validated.
+ * when `deep` is 1, we scan all the entries one by one. */
+int zipmapValidateIntegrity(unsigned char *zm, size_t size, int deep) {
+#define OUT_OF_RANGE(p) ( \
+ (p) < zm + 2 || \
+ (p) > zm + size - 1)
+ unsigned int l, s, e;
+
+ /* check that we can actually read the header (or ZIPMAP_END). */
+ if (size < 2)
+ return 0;
+
+ /* the last byte must be the terminator. */
+ if (zm[size-1] != ZIPMAP_END)
+ return 0;
+
+ if (!deep)
+ return 1;
+
+ unsigned int count = 0;
+ unsigned char *p = zm + 1; /* skip the count */
+ while(*p != ZIPMAP_END) {
+ /* read the field name length encoding type */
+ s = zipmapGetEncodedLengthSize(p);
+ /* make sure the entry length doesn't rech outside the edge of the zipmap */
+ if (OUT_OF_RANGE(p+s))
+ return 0;
+
+ /* read the field name length */
+ l = zipmapDecodeLength(p);
+ p += s; /* skip the encoded field size */
+ p += l; /* skip the field */
+
+ /* make sure the entry doesn't rech outside the edge of the zipmap */
+ if (OUT_OF_RANGE(p))
+ return 0;
+
+ /* read the value length encoding type */
+ s = zipmapGetEncodedLengthSize(p);
+ /* make sure the entry length doesn't rech outside the edge of the zipmap */
+ if (OUT_OF_RANGE(p+s))
+ return 0;
+
+ /* read the value length */
+ l = zipmapDecodeLength(p);
+ p += s; /* skip the encoded value size*/
+ e = *p++; /* skip the encoded free space (always encoded in one byte) */
+ p += l+e; /* skip the value and free space */
+ count++;
+
+ /* make sure the entry doesn't rech outside the edge of the zipmap */
+ if (OUT_OF_RANGE(p))
+ return 0;
+ }
+
+ /* check that the count in the header is correct */
+ if (zm[0] != ZIPMAP_BIGLEN && zm[0] != count)
+ return 0;
+
+ return 1;
+#undef OUT_OF_RANGE
+}
+
#ifdef REDIS_TEST
static void zipmapRepr(unsigned char *p) {
unsigned int l;