Explorar el Código

ncd: NCDVal: enforce a maximum value depth to avoid stack overflows

ambrop7 hace 13 años
padre
commit
62917f151e

+ 7 - 5
examples/ncdval_test.c

@@ -91,6 +91,8 @@ static void print_value (NCDValRef val, unsigned int indent)
 
 int main ()
 {
+    int res;
+    
     BLog_InitStdout();
     
     NCDStringIndex string_index;
@@ -113,8 +115,8 @@ int main ()
     NCDValRef l1 = NCDVal_NewList(&mem, 10);
     FORCE( !NCDVal_IsInvalid(l1) )
     
-    NCDVal_ListAppend(l1, s1);
-    NCDVal_ListAppend(l1, s2);
+    FORCE( NCDVal_ListAppend(l1, s1) )
+    FORCE( NCDVal_ListAppend(l1, s2) )
     
     print_value(s1, 0);
     print_value(s2, 0);
@@ -133,8 +135,8 @@ int main ()
     NCDValRef m1 = NCDVal_NewMap(&mem, 3);
     FORCE( !NCDVal_IsInvalid(m1) )
     
-    FORCE( NCDVal_MapInsert(m1, k1, v1) )
-    FORCE( NCDVal_MapInsert(m1, k2, v2) )
+    FORCE( NCDVal_MapInsert(m1, k1, v1, &res) && res )
+    FORCE( NCDVal_MapInsert(m1, k2, v2, &res) && res )
     
     ASSERT( NCDVal_MapGetValue(m1, "K1").idx == v1.idx )
     ASSERT( NCDVal_MapGetValue(m1, "K2").idx == v2.idx )
@@ -158,7 +160,7 @@ int main ()
     ASSERT( NCDVal_Type(ids2) == NCDVAL_STRING )
     ASSERT( NCDVal_IsIdString(ids2) )
     
-    FORCE( NCDVal_MapInsert(m1, ids1, ids2) )
+    FORCE( NCDVal_MapInsert(m1, ids1, ids2, &res) && res )
     
     ASSERT( NCDVal_MapGetValue(m1, "_arg1").idx == ids2.idx )
     

+ 10 - 2
ncd/NCDInterpProcess.c

@@ -102,7 +102,10 @@ static int convert_value_recurser (NCDPlaceholderDb *pdb, NCDStringIndex *string
                     goto fail;
                 }
                 
-                NCDVal_ListAppend(*out, vval);
+                if (!NCDVal_ListAppend(*out, vval)) {
+                    BLog(BLOG_ERROR, "depth limit exceeded");
+                    goto fail;
+                }
             }
         } break;
         
@@ -123,7 +126,12 @@ static int convert_value_recurser (NCDPlaceholderDb *pdb, NCDStringIndex *string
                     goto fail;
                 }
                 
-                if (!NCDVal_MapInsert(*out, vkey, vval)) {
+                int inserted;
+                if (!NCDVal_MapInsert(*out, vkey, vval, &inserted)) {
+                    BLog(BLOG_ERROR, "depth limit exceeded");
+                    goto fail;
+                }
+                if (!inserted) {
                     BLog(BLOG_ERROR, "duplicate key in map");
                     goto fail;
                 }

+ 4 - 1
ncd/NCDInterpreter.c

@@ -1313,7 +1313,10 @@ int statement_instance_func_interp_getargs (void *vinterp, NCDValMem *mem, NCDVa
             goto fail;
         }
         
-        NCDVal_ListAppend(*out_value, arg);
+        if (!NCDVal_ListAppend(*out_value, arg)) {
+            BLog(BLOG_ERROR, "depth limit exceeded");
+            goto fail;
+        }
     }
     
     return 1;

+ 196 - 53
ncd/NCDVal.c

@@ -45,10 +45,53 @@
 
 //#define NCDVAL_TEST_EXTERNAL_STRINGS
 
-#define EXTERNAL_TYPE_MASK ((1 << 3) - 1)
+#define TYPE_MASK_EXTERNAL_TYPE ((1 << 3) - 1)
+#define TYPE_MASK_INTERNAL_TYPE ((1 << 5) - 1)
+#define TYPE_SHIFT_DEPTH 5
+
 #define IDSTRING_TYPE (NCDVAL_STRING | (1 << 3))
 #define EXTERNALSTRING_TYPE (NCDVAL_STRING | (2 << 3))
 
+static int make_type (int internal_type, int depth)
+{
+    ASSERT(internal_type == NCDVAL_STRING ||
+           internal_type == NCDVAL_LIST ||
+           internal_type == NCDVAL_MAP ||
+           internal_type == IDSTRING_TYPE ||
+           internal_type == EXTERNALSTRING_TYPE)
+    ASSERT(depth >= 0)
+    ASSERT(depth <= NCDVAL_MAX_DEPTH)
+    
+    return (internal_type | (depth << TYPE_SHIFT_DEPTH));
+}
+
+static int get_external_type (int type)
+{
+    return (type & TYPE_MASK_EXTERNAL_TYPE);
+}
+
+static int get_internal_type (int type)
+{
+    return (type & TYPE_MASK_INTERNAL_TYPE);
+}
+
+static int get_depth (int type)
+{
+    return (type >> TYPE_SHIFT_DEPTH);
+}
+
+static int bump_depth (int *type_ptr, int elem_depth)
+{
+    if (get_depth(*type_ptr) < elem_depth + 1) {
+        if (elem_depth + 1 > NCDVAL_MAX_DEPTH) {
+            return 0;
+        }
+        *type_ptr = make_type(get_internal_type(*type_ptr), elem_depth + 1);
+    }
+    
+    return 1;
+}
+
 static void * NCDValMem__BufAt (NCDValMem *o, NCDVal__idx idx)
 {
     ASSERT(idx >= 0)
@@ -145,7 +188,10 @@ static void NCDVal__AssertValOnly (NCDValMem *mem, NCDVal__idx idx)
 #ifndef NDEBUG
     int *type_ptr = NCDValMem__BufAt(mem, idx);
     
-    switch (*type_ptr) {
+    ASSERT(get_depth(*type_ptr) >= 0)
+    ASSERT(get_depth(*type_ptr) <= NCDVAL_MAX_DEPTH)
+    
+    switch (get_internal_type(*type_ptr)) {
         case NCDVAL_STRING: {
             ASSERT(idx + sizeof(struct NCDVal__string) <= mem->used)
             struct NCDVal__string *str_e = NCDValMem__BufAt(mem, idx);
@@ -224,6 +270,23 @@ static NCDVal__idx NCDVal__MapElemIdx (NCDVal__idx mapidx, NCDVal__idx pos)
     return mapidx + offsetof(struct NCDVal__map, elems) + pos * sizeof(struct NCDVal__mapelem);
 }
 
+static int NCDVal__Depth (NCDValRef val)
+{
+    ASSERT(val.idx != -1)
+    
+    // handle placeholders
+    if (val.idx < 0) {
+        return 0;
+    }
+    
+    int *elem_type_ptr = NCDValMem__BufAt(val.mem, val.idx);
+    int depth = get_depth(*elem_type_ptr);
+    ASSERT(depth >= 0)
+    ASSERT(depth <= NCDVAL_MAX_DEPTH)
+    
+    return depth;
+}
+
 #include "NCDVal_maptree.h"
 #include <structure/CAvl_impl.h>
 
@@ -326,7 +389,7 @@ int NCDVal_Type (NCDValRef val)
     
     int *type_ptr = NCDValMem__BufAt(val.mem, val.idx);
     
-    return (*type_ptr & EXTERNAL_TYPE_MASK);
+    return get_external_type(*type_ptr);
 }
 
 NCDValRef NCDVal_NewInvalid (void)
@@ -363,7 +426,7 @@ NCDValRef NCDVal_NewCopy (NCDValMem *mem, NCDValRef val)
     
     void *ptr = NCDValMem__BufAt(val.mem, val.idx);
     
-    switch (*(int *)ptr) {
+    switch (get_internal_type(*(int *)ptr)) {
         case NCDVAL_STRING: {
             size_t len = NCDVal_StringLength(val);
             
@@ -391,7 +454,9 @@ NCDValRef NCDVal_NewCopy (NCDValMem *mem, NCDValRef val)
                     goto fail;
                 }
                 
-                NCDVal_ListAppend(copy, elem_copy);
+                if (!NCDVal_ListAppend(copy, elem_copy)) {
+                    goto fail;
+                }
             }
             
             return copy;
@@ -412,8 +477,11 @@ NCDValRef NCDVal_NewCopy (NCDValMem *mem, NCDValRef val)
                     goto fail;
                 }
                 
-                int res = NCDVal_MapInsert(copy, key_copy, val_copy);
-                ASSERT_EXECUTE(res)
+                int inserted;
+                if (!NCDVal_MapInsert(copy, key_copy, val_copy, &inserted)) {
+                    goto fail;
+                }
+                ASSERT_EXECUTE(inserted)
             }
             
             return copy;
@@ -566,14 +634,14 @@ int NCDVal_IsIdString (NCDValRef val)
 {
     NCDVal__AssertVal(val);
     
-    return !(val.idx < -1) && *(int *)NCDValMem__BufAt(val.mem, val.idx) == IDSTRING_TYPE;
+    return !(val.idx < -1) && get_internal_type(*(int *)NCDValMem__BufAt(val.mem, val.idx)) == IDSTRING_TYPE;
 }
 
 int NCDVal_IsExternalString (NCDValRef val)
 {
     NCDVal__AssertVal(val);
     
-    return !(val.idx < -1) && *(int *)NCDValMem__BufAt(val.mem, val.idx) == EXTERNALSTRING_TYPE;
+    return !(val.idx < -1) && get_internal_type(*(int *)NCDValMem__BufAt(val.mem, val.idx)) == EXTERNALSTRING_TYPE;
 }
 
 int NCDVal_IsStringNoNulls (NCDValRef val)
@@ -659,7 +727,7 @@ NCDValRef NCDVal_NewStringBin (NCDValMem *mem, const uint8_t *data, size_t len)
     }
     
     struct NCDVal__string *str_e = NCDValMem__BufAt(mem, idx);
-    str_e->type = NCDVAL_STRING;
+    str_e->type = make_type(NCDVAL_STRING, 0);
     str_e->length = len;
     if (len > 0) {
         memcpy(str_e->data, data, len);
@@ -689,7 +757,7 @@ NCDValRef NCDVal_NewStringUninitialized (NCDValMem *mem, size_t len)
     }
     
     struct NCDVal__string *str_e = NCDValMem__BufAt(mem, idx);
-    str_e->type = NCDVAL_STRING;
+    str_e->type = make_type(NCDVAL_STRING, 0);
     str_e->length = len;
     str_e->data[len] = '\0';
     
@@ -712,7 +780,7 @@ NCDValRef NCDVal_NewIdString (NCDValMem *mem, NCD_string_id_t string_id, NCDStri
     }
     
     struct NCDVal__idstring *ids_e = NCDValMem__BufAt(mem, idx);
-    ids_e->type = IDSTRING_TYPE;
+    ids_e->type = make_type(IDSTRING_TYPE, 0);
     ids_e->string_id = string_id;
     ids_e->string_index = string_index;
     
@@ -742,7 +810,7 @@ NCDValRef NCDVal_NewExternalString (NCDValMem *mem, const char *data, size_t len
     }
     
     struct NCDVal__externalstring *exs_e = NCDValMem__BufAt(mem, idx);
-    exs_e->type = EXTERNALSTRING_TYPE;
+    exs_e->type = make_type(EXTERNALSTRING_TYPE, 0);
     exs_e->data = data;
     exs_e->length = len;
     exs_e->ref.target = ref_target;
@@ -764,7 +832,7 @@ const char * NCDVal_StringData (NCDValRef string)
     
     void *ptr = NCDValMem__BufAt(string.mem, string.idx);
     
-    switch (*(int *)ptr) {
+    switch (get_internal_type(*(int *)ptr)) {
         case IDSTRING_TYPE: {
             struct NCDVal__idstring *ids_e = ptr;
             const char *value = NCDStringIndex_Value(ids_e->string_index, ids_e->string_id);
@@ -787,7 +855,7 @@ size_t NCDVal_StringLength (NCDValRef string)
     
     void *ptr = NCDValMem__BufAt(string.mem, string.idx);
     
-    switch (*(int *)ptr) {
+    switch (get_internal_type(*(int *)ptr)) {
         case IDSTRING_TYPE: {
             struct NCDVal__idstring *ids_e = ptr;
             return NCDStringIndex_Length(ids_e->string_index, ids_e->string_id);
@@ -810,7 +878,7 @@ int NCDVal_StringNullTerminate (NCDValRef string, NCDValNullTermString *out)
     
     void *ptr = NCDValMem__BufAt(string.mem, string.idx);
     
-    switch (*(int *)ptr) {
+    switch (get_internal_type(*(int *)ptr)) {
         case IDSTRING_TYPE: {
             struct NCDVal__idstring *ids_e = ptr;
             out->data = (char *)NCDStringIndex_Value(ids_e->string_index, ids_e->string_id);
@@ -895,7 +963,7 @@ int NCDVal_StringHasNulls (NCDValRef string)
     
     void *ptr = NCDValMem__BufAt(string.mem, string.idx);
     
-    switch (*(int *)ptr) {
+    switch (get_internal_type(*(int *)ptr)) {
         case IDSTRING_TYPE: {
             struct NCDVal__idstring *ids_e = ptr;
             return NCDStringIndex_HasNulls(ids_e->string_index, ids_e->string_id);
@@ -928,7 +996,7 @@ int NCDVal_StringEqualsId (NCDValRef string, NCD_string_id_t string_id,
     
     void *ptr = NCDValMem__BufAt(string.mem, string.idx);
     
-    switch (*(int *)ptr) {
+    switch (get_internal_type(*(int *)ptr)) {
         case IDSTRING_TYPE: {
             struct NCDVal__idstring *ids_e = ptr;
             ASSERT(ids_e->string_index == string_index)
@@ -967,7 +1035,7 @@ NCDValRef NCDVal_NewList (NCDValMem *mem, size_t maxcount)
     }
     
     struct NCDVal__list *list_e = NCDValMem__BufAt(mem, idx);
-    list_e->type = NCDVAL_LIST;
+    list_e->type = make_type(NCDVAL_LIST, 0);
     list_e->maxcount = maxcount;
     list_e->count = 0;
     
@@ -977,7 +1045,7 @@ fail:
     return NCDVal_NewInvalid();
 }
 
-void NCDVal_ListAppend (NCDValRef list, NCDValRef elem)
+int NCDVal_ListAppend (NCDValRef list, NCDValRef elem)
 {
     ASSERT(NCDVal_IsList(list))
     ASSERT(NCDVal_ListCount(list) < NCDVal_ListMaxCount(list))
@@ -986,7 +1054,13 @@ void NCDVal_ListAppend (NCDValRef list, NCDValRef elem)
     
     struct NCDVal__list *list_e = NCDValMem__BufAt(list.mem, list.idx);
     
+    if (!bump_depth(&list_e->type, NCDVal__Depth(elem))) {
+        return 0;
+    }
+    
     list_e->elem_indices[list_e->count++] = elem.idx;
+    
+    return 1;
 }
 
 size_t NCDVal_ListCount (NCDValRef list)
@@ -1086,7 +1160,7 @@ NCDValRef NCDVal_NewMap (NCDValMem *mem, size_t maxcount)
     }
     
     struct NCDVal__map *map_e = NCDValMem__BufAt(mem, idx);
-    map_e->type = NCDVAL_MAP;
+    map_e->type = make_type(NCDVAL_MAP, 0);
     map_e->maxcount = maxcount;
     map_e->count = 0;
     NCDVal__MapTree_Init(&map_e->tree);
@@ -1097,7 +1171,7 @@ fail:
     return NCDVal_NewInvalid();
 }
 
-int NCDVal_MapInsert (NCDValRef map, NCDValRef key, NCDValRef val)
+int NCDVal_MapInsert (NCDValRef map, NCDValRef key, NCDValRef val, int *out_inserted)
 {
     ASSERT(NCDVal_IsMap(map))
     ASSERT(NCDVal_MapCount(map) < NCDVal_MapMaxCount(map))
@@ -1108,6 +1182,11 @@ int NCDVal_MapInsert (NCDValRef map, NCDValRef key, NCDValRef val)
     
     struct NCDVal__map *map_e = NCDValMem__BufAt(map.mem, map.idx);
     
+    int new_type = map_e->type;
+    if (!bump_depth(&new_type, NCDVal__Depth(key)) || !bump_depth(&new_type, NCDVal__Depth(val))) {
+        return 0;
+    }
+    
     NCDVal__idx elemidx = NCDVal__MapElemIdx(map.idx, map_e->count);
     
     struct NCDVal__mapelem *me_e = NCDValMem__BufAt(map.mem, elemidx);
@@ -1117,11 +1196,18 @@ int NCDVal_MapInsert (NCDValRef map, NCDValRef key, NCDValRef val)
     
     int res = NCDVal__MapTree_Insert(&map_e->tree, map.mem, NCDVal__MapTreeDeref(map.mem, elemidx), NULL);
     if (!res) {
-        return 0;
+        if (out_inserted) {
+            *out_inserted = 0;
+        }
+        return 1;
     }
     
+    map_e->type = new_type;
     map_e->count++;
     
+    if (out_inserted) {
+        *out_inserted = 1;
+    }
     return 1;
 }
 
@@ -1253,7 +1339,7 @@ NCDValRef NCDVal_MapGetValue (NCDValRef map, const char *key_str)
     mem.first_ref = -1;
     
     struct NCDVal__externalstring *exs_e = (void *)mem.fastbuf;
-    exs_e->type = EXTERNALSTRING_TYPE;
+    exs_e->type = make_type(EXTERNALSTRING_TYPE, 0);
     exs_e->data = key_str;
     exs_e->length = strlen(key_str);
     exs_e->ref.target = NULL;
@@ -1280,7 +1366,7 @@ static void replaceprog_build_recurser (NCDValMem *mem, NCDVal__idx idx, size_t
     
     struct NCDVal__instr instr;
     
-    switch (*((int *)(ptr))) {
+    switch (get_internal_type(*((int *)(ptr)))) {
         case NCDVAL_STRING:
         case IDSTRING_TYPE:
         case EXTERNALSTRING_TYPE: {
@@ -1290,6 +1376,8 @@ static void replaceprog_build_recurser (NCDValMem *mem, NCDVal__idx idx, size_t
             struct NCDVal__list *list_e = ptr;
             
             for (NCDVal__idx i = 0; i < list_e->count; i++) {
+                int elem_changed = 0;
+                
                 if (list_e->elem_indices[i] < -1) {
                     if (prog) {
                         instr.type = NCDVAL_INSTR_PLACEHOLDER;
@@ -1298,10 +1386,24 @@ static void replaceprog_build_recurser (NCDValMem *mem, NCDVal__idx idx, size_t
                         prog->instrs[prog->num_instrs++] = instr;
                     }
                     (*out_num_instr)++;
+                    elem_changed = 1;
                 } else {
                     size_t elem_num_instr;
                     replaceprog_build_recurser(mem, list_e->elem_indices[i], &elem_num_instr, prog);
                     (*out_num_instr) += elem_num_instr;
+                    if (elem_num_instr > 0) {
+                        elem_changed = 1;
+                    }
+                }
+                
+                if (elem_changed) {
+                    if (prog) {
+                        instr.type = NCDVAL_INSTR_BUMPDEPTH;
+                        instr.bumpdepth.parent_idx = idx;
+                        instr.bumpdepth.child_idx_idx = idx + offsetof(struct NCDVal__list, elem_indices) + i * sizeof(NCDVal__idx);
+                        prog->instrs[prog->num_instrs++] = instr;
+                    }
+                    (*out_num_instr)++;
                 }
             }
         } break;
@@ -1310,7 +1412,8 @@ static void replaceprog_build_recurser (NCDValMem *mem, NCDVal__idx idx, size_t
             struct NCDVal__map *map_e = ptr;
             
             for (NCDVal__idx i = 0; i < map_e->count; i++) {
-                int need_reinsert = 0;
+                int key_changed = 0;
+                int val_changed = 0;
                 
                 if (map_e->elems[i].key_idx < -1) {
                     if (prog) {
@@ -1320,13 +1423,13 @@ static void replaceprog_build_recurser (NCDValMem *mem, NCDVal__idx idx, size_t
                         prog->instrs[prog->num_instrs++] = instr;
                     }
                     (*out_num_instr)++;
-                    need_reinsert = 1;
+                    key_changed = 1;
                 } else {
                     size_t key_num_instr;
                     replaceprog_build_recurser(mem, map_e->elems[i].key_idx, &key_num_instr, prog);
                     (*out_num_instr) += key_num_instr;
                     if (key_num_instr > 0) {
-                        need_reinsert = 1;
+                        key_changed = 1;
                     }
                 }
                 
@@ -1338,13 +1441,17 @@ static void replaceprog_build_recurser (NCDValMem *mem, NCDVal__idx idx, size_t
                         prog->instrs[prog->num_instrs++] = instr;
                     }
                     (*out_num_instr)++;
+                    val_changed = 1;
                 } else {
                     size_t val_num_instr;
                     replaceprog_build_recurser(mem, map_e->elems[i].val_idx, &val_num_instr, prog);
                     (*out_num_instr) += val_num_instr;
+                    if (val_num_instr > 0) {
+                        val_changed = 1;
+                    }
                 }
                 
-                if (need_reinsert) {
+                if (key_changed) {
                     if (prog) {
                         instr.type = NCDVAL_INSTR_REINSERT;
                         instr.reinsert.mapidx = idx;
@@ -1352,6 +1459,24 @@ static void replaceprog_build_recurser (NCDValMem *mem, NCDVal__idx idx, size_t
                         prog->instrs[prog->num_instrs++] = instr;
                     }
                     (*out_num_instr)++;
+                    
+                    if (prog) {
+                        instr.type = NCDVAL_INSTR_BUMPDEPTH;
+                        instr.bumpdepth.parent_idx = idx;
+                        instr.bumpdepth.child_idx_idx = idx + offsetof(struct NCDVal__map, elems) + i * sizeof(struct NCDVal__mapelem) + offsetof(struct NCDVal__mapelem, key_idx);
+                        prog->instrs[prog->num_instrs++] = instr;
+                    }
+                    (*out_num_instr)++;
+                }
+                
+                if (val_changed) {
+                    if (prog) {
+                        instr.type = NCDVAL_INSTR_BUMPDEPTH;
+                        instr.bumpdepth.parent_idx = idx;
+                        instr.bumpdepth.child_idx_idx = idx + offsetof(struct NCDVal__map, elems) + i * sizeof(struct NCDVal__mapelem) + offsetof(struct NCDVal__mapelem, val_idx);
+                        prog->instrs[prog->num_instrs++] = instr;
+                    }
+                    (*out_num_instr)++;
                 }
             }
         } break;
@@ -1397,35 +1522,53 @@ int NCDValReplaceProg_Execute (NCDValReplaceProg prog, NCDValMem *mem, NCDVal_re
     for (size_t i = 0; i < prog.num_instrs; i++) {
         struct NCDVal__instr instr = prog.instrs[i];
         
-        if (instr.type == NCDVAL_INSTR_PLACEHOLDER) {
+        switch (instr.type) {
+            case NCDVAL_INSTR_PLACEHOLDER: {
 #ifndef NDEBUG
-            NCDVal__idx *check_plptr = NCDValMem__BufAt(mem, instr.placeholder.plidx);
-            ASSERT(*check_plptr < -1)
-            ASSERT(*check_plptr - NCDVAL_MINIDX == instr.placeholder.plid)
+                NCDVal__idx *check_plptr = NCDValMem__BufAt(mem, instr.placeholder.plidx);
+                ASSERT(*check_plptr < -1)
+                ASSERT(*check_plptr - NCDVAL_MINIDX == instr.placeholder.plid)
 #endif
-            NCDValRef repval;
-            if (!replace(arg, instr.placeholder.plid, mem, &repval) || NCDVal_IsInvalid(repval)) {
-                return 0;
-            }
-            ASSERT(repval.mem == mem)
+                NCDValRef repval;
+                if (!replace(arg, instr.placeholder.plid, mem, &repval) || NCDVal_IsInvalid(repval)) {
+                    return 0;
+                }
+                ASSERT(repval.mem == mem)
+                
+                NCDVal__idx *plptr = NCDValMem__BufAt(mem, instr.placeholder.plidx);
+                *plptr = repval.idx;
+            } break;
             
-            NCDVal__idx *plptr = NCDValMem__BufAt(mem, instr.placeholder.plidx);
-            *plptr = repval.idx;
-        } else {
-            ASSERT(instr.type == NCDVAL_INSTR_REINSERT)
+            case NCDVAL_INSTR_REINSERT: {
+                NCDVal__AssertValOnly(mem, instr.reinsert.mapidx);
+                struct NCDVal__map *map_e = NCDValMem__BufAt(mem, instr.reinsert.mapidx);
+                ASSERT(map_e->type == NCDVAL_MAP)
+                ASSERT(instr.reinsert.elempos >= 0)
+                ASSERT(instr.reinsert.elempos < map_e->count)
+                
+                NCDVal__MapTreeRef ref = {&map_e->elems[instr.reinsert.elempos], NCDVal__MapElemIdx(instr.reinsert.mapidx, instr.reinsert.elempos)};
+                NCDVal__MapTree_Remove(&map_e->tree, mem, ref);
+                if (!NCDVal__MapTree_Insert(&map_e->tree, mem, ref, NULL)) {
+                    BLog(BLOG_ERROR, "duplicate key in map");
+                    return 0;
+                }
+            } break;
             
-            NCDVal__AssertValOnly(mem, instr.reinsert.mapidx);
-            struct NCDVal__map *map_e = NCDValMem__BufAt(mem, instr.reinsert.mapidx);
-            ASSERT(map_e->type == NCDVAL_MAP)
-            ASSERT(instr.reinsert.elempos >= 0)
-            ASSERT(instr.reinsert.elempos < map_e->count)
+            case NCDVAL_INSTR_BUMPDEPTH: {
+                NCDVal__AssertValOnly(mem, instr.bumpdepth.parent_idx);
+                int *parent_type_ptr = NCDValMem__BufAt(mem, instr.bumpdepth.parent_idx);
+                
+                NCDVal__idx *child_type_idx_ptr = NCDValMem__BufAt(mem, instr.bumpdepth.child_idx_idx);
+                NCDVal__AssertValOnly(mem, *child_type_idx_ptr);
+                int *child_type_ptr = NCDValMem__BufAt(mem, *child_type_idx_ptr);
+                
+                if (!bump_depth(parent_type_ptr, get_depth(*child_type_ptr))) {
+                    BLog(BLOG_ERROR, "depth limit exceeded");
+                    return 0;
+                }
+            } break;
             
-            NCDVal__MapTreeRef ref = {&map_e->elems[instr.reinsert.elempos], NCDVal__MapElemIdx(instr.reinsert.mapidx, instr.reinsert.elempos)};
-            NCDVal__MapTree_Remove(&map_e->tree, mem, ref);
-            if (!NCDVal__MapTree_Insert(&map_e->tree, mem, ref, NULL)) {
-                BLog(BLOG_ERROR, "duplicate key in map");
-                return 0;
-            }
+            default: ASSERT(0);
         }
     }
     

+ 16 - 4
ncd/NCDVal.h

@@ -42,6 +42,7 @@
 
 #define NCDVAL_FASTBUF_SIZE 64
 #define NCDVAL_FIRST_SIZE 256
+#define NCDVAL_MAX_DEPTH 32
 
 #define NCDVAL_MAXIDX INT_MAX
 #define NCDVAL_MINIDX INT_MIN
@@ -132,6 +133,7 @@ typedef struct {
 
 #define NCDVAL_INSTR_PLACEHOLDER 0
 #define NCDVAL_INSTR_REINSERT 1
+#define NCDVAL_INSTR_BUMPDEPTH 2
 
 struct NCDVal__instr {
     int type;
@@ -144,6 +146,10 @@ struct NCDVal__instr {
             NCDVal__idx mapidx;
             NCDVal__idx elempos;
         } reinsert;
+        struct {
+            NCDVal__idx parent_idx;
+            NCDVal__idx child_idx_idx;
+        } bumpdepth;
     };
 };
 
@@ -490,8 +496,11 @@ NCDValRef NCDVal_NewList (NCDValMem *mem, size_t maxcount);
  * the same memory object as the list.
  * Inserting a value into a list does not in any way change it;
  * internally, the list only points to it.
+ * You must not modify the element after it has been inserted into the
+ * list.
+ * Returns 1 on success and 0 on failure (depth limit exceeded).
  */
-void NCDVal_ListAppend (NCDValRef list, NCDValRef elem);
+int NCDVal_ListAppend (NCDValRef list, NCDValRef elem) WARN_UNUSED;
 
 /**
  * Returns the number of elements in a list value, i.e. the number
@@ -556,10 +565,13 @@ NCDValRef NCDVal_NewMap (NCDValMem *mem, size_t maxcount);
  * internally, the map only points to it.
  * You must not modify the key after inserting it into a map. This is because
  * the map builds an embedded AVL tree of entries indexed by keys.
- * If 'key' does not exist in the map, succeeds, returning 1.
- * If 'key' already exists in the map, fails, returning 0.
+ * If insertion fails due to a maximum depth limit, returns 0.
+ * Otherwise returns 1, and *out_inserted is set to 1 if the key did not
+ * yet exist and the entry was inserted, and to 0 if it did exist and the
+ * entry was not inserted. The 'out_inserted' pointer may be NULL, in which
+ * case *out_inserted is never set.
  */
-int NCDVal_MapInsert (NCDValRef map, NCDValRef key, NCDValRef val);
+int NCDVal_MapInsert (NCDValRef map, NCDValRef key, NCDValRef val, int *out_inserted) WARN_UNUSED;
 
 /**
  * Returns the number of entries in a map value, i.e. the number

+ 8 - 2
ncd/NCDValCons.c

@@ -94,7 +94,9 @@ static int complete_value (NCDValCons *o, NCDValConsVal val, NCDValSafeRef *out,
                 
                 NCDValRef elem = NCDVal_FromSafe(o->mem, o->elems[elemidx].ref);
                 
-                NCDVal_ListAppend(list, elem);
+                if (!NCDVal_ListAppend(list, elem)) {
+                    goto fail_memory;
+                }
                 
                 elemidx = o->elems[elemidx].next;
             }
@@ -121,7 +123,11 @@ static int complete_value (NCDValCons *o, NCDValConsVal val, NCDValSafeRef *out,
                 NCDValRef key = NCDVal_FromSafe(o->mem, o->elems[keyidx].ref);
                 NCDValRef value = NCDVal_FromSafe(o->mem, o->elems[validx].ref);
                 
-                if (!NCDVal_MapInsert(map, key, value)) {
+                int inserted;
+                if (!NCDVal_MapInsert(map, key, value, &inserted)) {
+                    goto fail_memory;
+                }
+                if (!inserted) {
                     *out_error = NCDVALCONS_ERROR_DUPLICATE_KEY;
                     return 0;
                 }

+ 21 - 7
ncd/extra/address_utils.h

@@ -150,7 +150,9 @@ static NCDValRef ncd_make_baddr (BAddr addr, NCDValMem *mem)
                 goto fail;
             }
             
-            NCDVal_ListAppend(val, type_val);
+            if (!NCDVal_ListAppend(val, type_val)) {
+                goto fail;
+            }
         } break;
         
         case BADDR_TYPE_IPV4: {
@@ -176,9 +178,15 @@ static NCDValRef ncd_make_baddr (BAddr addr, NCDValMem *mem)
                 goto fail;
             }
             
-            NCDVal_ListAppend(val, type_val);
-            NCDVal_ListAppend(val, ipaddr_val);
-            NCDVal_ListAppend(val, port_val);
+            if (!NCDVal_ListAppend(val, type_val)) {
+                goto fail;
+            }
+            if (!NCDVal_ListAppend(val, ipaddr_val)) {
+                goto fail;
+            }
+            if (!NCDVal_ListAppend(val, port_val)) {
+                goto fail;
+            }
         } break;
         
         case BADDR_TYPE_IPV6: {
@@ -206,9 +214,15 @@ static NCDValRef ncd_make_baddr (BAddr addr, NCDValMem *mem)
                 goto fail;
             }
             
-            NCDVal_ListAppend(val, type_val);
-            NCDVal_ListAppend(val, ipaddr_val);
-            NCDVal_ListAppend(val, port_val);
+            if (!NCDVal_ListAppend(val, type_val)) {
+                goto fail;
+            }
+            if (!NCDVal_ListAppend(val, ipaddr_val)) {
+                goto fail;
+            }
+            if (!NCDVal_ListAppend(val, port_val)) {
+                goto fail;
+            }
         } break;
     }
     

+ 4 - 1
ncd/modules/explode.c

@@ -204,7 +204,10 @@ static int func_getvar2 (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValR
                 ModuleLog(o->i, BLOG_ERROR, "NCDVal_NewStringBin failed");
                 goto fail;
             }
-            NCDVal_ListAppend(*out, str);
+            if (!NCDVal_ListAppend(*out, str)) {
+                ModuleLog(o->i, BLOG_ERROR, "NCDVal_ListAppend failed");
+                goto fail;
+            }
         }
         return 1;
     }

+ 4 - 1
ncd/modules/list.c

@@ -308,7 +308,10 @@ static int list_to_value (NCDModuleInst *i, struct instance *o, NCDValMem *mem,
             goto fail;
         }
         
-        NCDVal_ListAppend(*out_val, copy);
+        if (!NCDVal_ListAppend(*out_val, copy)) {
+            ModuleLog(i, BLOG_ERROR, "depth limit exceeded");
+            goto fail;
+        }
     }
     
     return 1;

+ 4 - 1
ncd/modules/net_ipv4_dhcp.c

@@ -319,7 +319,10 @@ static int func_getvar (void *vo, const char *name, NCDValMem *mem, NCDValRef *o
                 goto fail;
             }
             
-            NCDVal_ListAppend(*out, server);
+            if (!NCDVal_ListAppend(*out, server)) {
+                ModuleLog(o->i, BLOG_ERROR, "NCDVal_ListAppend failed");
+                goto fail;
+            }
         }
         
         return 1;

+ 14 - 4
ncd/modules/value.c

@@ -697,7 +697,10 @@ static int value_to_value (NCDModuleInst *i, struct value *v, NCDValMem *mem, NC
                     goto fail;
                 }
                 
-                NCDVal_ListAppend(*out_value, eval);
+                if (!NCDVal_ListAppend(*out_value, eval)) {
+                    ModuleLog(i, BLOG_ERROR, "depth limit exceeded");
+                    goto fail;
+                }
             }
         } break;
         
@@ -722,8 +725,12 @@ static int value_to_value (NCDModuleInst *i, struct value *v, NCDValMem *mem, NC
                     goto fail;
                 }
                 
-                int res = NCDVal_MapInsert(*out_value, key, val);
-                ASSERT_EXECUTE(res)
+                int inserted;
+                if (!NCDVal_MapInsert(*out_value, key, val, &inserted)) {
+                    ModuleLog(i, BLOG_ERROR, "depth limit exceeded");
+                    goto fail;
+                }
+                ASSERT_EXECUTE(inserted)
             }
         } break;
         
@@ -1176,7 +1183,10 @@ static int func_getvar2 (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValR
                 goto fail;
             }
             
-            NCDVal_ListAppend(*out, key);
+            if (!NCDVal_ListAppend(*out, key)) {
+                ModuleLog(o->i, BLOG_ERROR, "depth limit exceeded");
+                goto fail;
+            }
         }
     }
     else if (name == NCD_STRING_EMPTY) {