Repository: ultrajson/ultrajson (language: python)

Issue:
Segmentation fault in list and dict dumping when an element exactly consumes the buffer
### What did you do?
`python3 -c 'import ujson; ujson.dumps(["aaaa", "\x00" * 10921])'`

### What did you expect to happen?
No crash

### What actually happened?
SIGSEGV

### What versions are you using?

* OS: Debian Sid
* Python: 3.10.1
* UltraJSON: 316d384f

### Background
The input here is constructed to exactly hit the buffer boundary. To better see what's going on, I added some `fprintf` statements in the `Buffer_Reserve` macro:

```diff
diff --git a/lib/ultrajsonenc.c b/lib/ultrajsonenc.c
index a9f3ef1..874d332 100644
--- a/lib/ultrajsonenc.c
+++ b/lib/ultrajsonenc.c
@@ -488,8 +488,10 @@ static int Buffer_EscapeStringValidated (JSOBJ obj, JSONObjectEncoder *enc, cons
 }
 
 #define Buffer_Reserve(__enc, __len) \
+    fprintf(stderr, "reserve %zu, remaining %zu\n", (size_t) (__len), (size_t) ((__enc)->end - (__enc)->offset)); \
     if ( (size_t) ((__enc)->end - (__enc)->offset) < (size_t) (__len))  \
     {   \
+      fprintf(stderr, "realloc\n"); \
       Buffer_Realloc((__enc), (__len));\
     }   \
 
```

With the command above, the output is this, with comments of what they correspond to:

* reserve 258, remaining 65536 – initial call for encoding the list; evidently, the initial buffer size is 64 KiB (coming from `objToJSON`)
* reserve 258, remaining 65535 – pre-`name` call on first list element; as there is no `name`, this call is useless
* reserve 26, remaining 65535 – `"aaaa"` reservation
* reserve 258, remaining 65528 – pre-`name` call on second list element
* reserve 65528, remaining 65528 – `"\x00" * 10921` reservation; note that this exactly consumes the rest of the buffer. Following this, the `]` gets written beyond the end of the buffer.
* reserve 1, remaining 18446744073709551615 – terminating `NUL` reservation, overflow in the remaining buffer size calculation
* Segmentation fault

As you might expect, everything is fine when the first string in the input list is one character shorter or longer because the reallocation condition is then triggered instead of overrunning the buffer. Of course, the list doesn't have to terminate after that long NUL string, and anything following that will overrun the buffer as well, e.g. `python3 -c 'import ujson; ujson.dumps(["aaaa", "\x00" * 10921, 42])'`.

The exact same thing is also possible with a dict, of course. For example, `python3 -c 'import ujson; ujson.dumps({"a": None, "b": "\x00" * 10920})'`. Just like before, the long NUL string exactly consumes the remaining buffer, and then the trailing `}` and `NUL` writes overrun it.


Discussion / hints:
Again, it's being fixed. Give me a chance... :stuck_out_tongue_closed_eyes: 
🙃 I'll stop looking now.

Fix patch:
diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml
new file mode 100644
index 00000000..23bcad52
--- /dev/null
+++ b/.github/workflows/fuzz.yml
@@ -0,0 +1,19 @@
+name: Fuzz
+
+on: [push, pull_request, workflow_dispatch]
+
+jobs:
+  test:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v3
+
+      - name: Install
+        run: |
+          python -m pip install -U pip
+          python -m pip install .
+        env:
+          CFLAGS: '-DDEBUG'
+
+      - name: Fuzz
+        run: python tests/fuzz.py --seed=0:1000
diff --git a/lib/ultrajson.h b/lib/ultrajson.h
index 8334fcae..c5d75b17 100644
--- a/lib/ultrajson.h
+++ b/lib/ultrajson.h
@@ -56,9 +56,6 @@ tree doesn't have cyclic references.
 #include <stdio.h>
 #include <wchar.h>
 
-// Don't output any extra whitespaces when encoding
-#define JSON_NO_EXTRA_WHITESPACE
-
 // Max decimals to encode double floating point numbers with
 #ifndef JSON_DOUBLE_MAX_DECIMALS
     #define JSON_DOUBLE_MAX_DECIMALS 15
diff --git a/lib/ultrajsonenc.c b/lib/ultrajsonenc.c
index 0c6c3ade..c8756136 100644
--- a/lib/ultrajsonenc.c
+++ b/lib/ultrajsonenc.c
@@ -41,6 +41,7 @@ Numeric decoder derived from from TCL library
 #include <assert.h>
 #include <string.h>
 #include <stdlib.h>
+#include <stddef.h>
 #include <math.h>
 
 #include <float.h>
@@ -113,14 +114,25 @@ FIXME: Keep track of how big these get across several encoder calls and try to m
 That way we won't run our head into the wall each call */
 static void Buffer_Realloc (JSONObjectEncoder *enc, size_t cbNeeded)
 {
+  size_t free_space = enc->end - enc->offset;
+  if (free_space >= cbNeeded)
+  {
+    return;
+  }
   size_t curSize = enc->end - enc->start;
-  size_t newSize = curSize * 2;
+  size_t newSize = curSize;
   size_t offset = enc->offset - enc->start;
 
+#ifdef DEBUG
+  // In debug mode, allocate only what is requested so that any miscalculation
+  // shows up plainly as a crash.
+  newSize = (enc->offset - enc->start) + cbNeeded;
+#else
   while (newSize < curSize + cbNeeded)
   {
     newSize *= 2;
   }
+#endif
 
   if (enc->heap)
   {
@@ -147,6 +159,12 @@ static void Buffer_Realloc (JSONObjectEncoder *enc, size_t cbNeeded)
   enc->end = enc->start + newSize;
 }
 
+#define Buffer_Reserve(__enc, __len) \
+    if ( (size_t) ((__enc)->end - (__enc)->offset) < (size_t) (__len))  \
+    {   \
+      Buffer_Realloc((__enc), (__len));\
+    }   \
+
 static FASTCALL_ATTR INLINE_PREFIX void FASTCALL_MSVC Buffer_AppendShortHexUnchecked (char *outputOffset, unsigned short value)
 {
   *(outputOffset++) = g_hexChars[(value & 0xf000) >> 12];
@@ -266,6 +284,13 @@ static int Buffer_EscapeStringValidated (JSOBJ obj, JSONObjectEncoder *enc, cons
 
   for (;;)
   {
+#ifdef DEBUG
+  // 6 is the maximum length of a single character (cf. RESERVE_STRING).
+  if ((io < end) && (enc->end - of < 6)) {
+    fprintf(stderr, "Ran out of buffer space during Buffer_EscapeStringValidated()\n");
+    abort();
+  }
+#endif
     JSUINT8 utflen = g_asciiOutputTable[(unsigned char) *io];
 
     switch (utflen)
@@ -487,15 +512,28 @@ static int Buffer_EscapeStringValidated (JSOBJ obj, JSONObjectEncoder *enc, cons
   }
 }
 
-#define Buffer_Reserve(__enc, __len) \
-    if ( (size_t) ((__enc)->end - (__enc)->offset) < (size_t) (__len))  \
-    {   \
-      Buffer_Realloc((__enc), (__len));\
-    }   \
-
 
-#define Buffer_AppendCharUnchecked(__enc, __chr) \
-                *((__enc)->offset++) = __chr; \
+static FASTCALL_ATTR INLINE_PREFIX void FASTCALL_MSVC Buffer_AppendCharUnchecked(JSONObjectEncoder *enc, char chr)
+{
+#ifdef DEBUG
+  if (enc->end <= enc->offset)
+  {
+    fprintf(stderr, "Overflow writing byte %d '%c'. The last few characters were:\n'''", chr, chr);
+    char * recent = enc->offset - 1000;
+    if (enc->start > recent)
+    {
+      recent = enc->start;
+    }
+    for (; recent < enc->offset; recent++)
+    {
+      fprintf(stderr, "%c", *recent);
+    }
+    fprintf(stderr, "'''\n");
+    abort();
+  }
+#endif
+  *(enc->offset++) = chr;
+}
 
 static FASTCALL_ATTR INLINE_PREFIX void FASTCALL_MSVC strreverse(char* begin, char* end)
 {
@@ -588,14 +626,6 @@ static void encode(JSOBJ obj, JSONObjectEncoder *enc, const char *name, size_t c
     return;
   }
 
-  /*
-  This reservation must hold
-
-  length of _name as encoded worst case +
-  maxLength of double to string OR maxLength of JSLONG to string
-  */
-
-  Buffer_Reserve(enc, 256 + RESERVE_STRING(cbName));
   if (enc->errorMsg)
   {
     return;
@@ -603,6 +633,8 @@ static void encode(JSOBJ obj, JSONObjectEncoder *enc, const char *name, size_t c
 
   if (name)
   {
+    // 2 extra for the colon and optional space after it
+    Buffer_Reserve(enc, RESERVE_STRING(cbName) + 2);
     Buffer_AppendCharUnchecked(enc, '\"');
 
     if (enc->forceASCII)
@@ -623,19 +655,26 @@ static void encode(JSOBJ obj, JSONObjectEncoder *enc, const char *name, size_t c
     Buffer_AppendCharUnchecked(enc, '\"');
 
     Buffer_AppendCharUnchecked (enc, ':');
-#ifdef JSON_NO_EXTRA_WHITESPACE
     if (enc->indent)
     {
       Buffer_AppendCharUnchecked (enc, ' ');
     }
-#else
-    Buffer_AppendCharUnchecked (enc, ' ');
-#endif
   }
 
   tc.encoder_prv = enc->prv;
   enc->beginTypeContext(obj, &tc, enc);
 
+  /*
+  This reservation covers any additions on non-variable parts below, specifically:
+  - Opening brackets for JT_ARRAY and JT_OBJECT
+  - Number representation for JT_LONG, JT_ULONG, JT_INT, and JT_DOUBLE
+  - Constant value for JT_TRUE, JT_FALSE, JT_NULL
+
+  The length of 128 is the worst case length of the Buffer_AppendDoubleDconv addition.
+  The other types above all have smaller representations.
+  */
+  Buffer_Reserve (enc, 128);
+
   switch (tc.type)
   {
     case JT_INVALID:
@@ -658,12 +697,12 @@ static void encode(JSOBJ obj, JSONObjectEncoder *enc, const char *name, size_t c
 
       while (enc->iterNext(obj, &tc))
       {
+        // The extra 2 bytes cover the comma and (optional) newline.
+        Buffer_Reserve (enc, enc->indent * (enc->level + 1) + 2);
+
         if (count > 0)
         {
           Buffer_AppendCharUnchecked (enc, ',');
-#ifndef JSON_NO_EXTRA_WHITESPACE
-          Buffer_AppendCharUnchecked (enc, ' ');
-#endif
         }
         Buffer_AppendIndentNewlineUnchecked (enc);
 
@@ -685,9 +724,12 @@ static void encode(JSOBJ obj, JSONObjectEncoder *enc, const char *name, size_t c
       enc->iterEnd(obj, &tc);
 
       if (count > 0) {
+        // Reserve space for the indentation plus the newline.
+        Buffer_Reserve (enc, enc->indent * enc->level + 1);
         Buffer_AppendIndentNewlineUnchecked (enc);
         Buffer_AppendIndentUnchecked (enc, enc->level);
       }
+      Buffer_Reserve (enc, 1);
       Buffer_AppendCharUnchecked (enc, ']');
       break;
     }
@@ -700,6 +742,9 @@ static void encode(JSOBJ obj, JSONObjectEncoder *enc, const char *name, size_t c
 
       while ((res = enc->iterNext(obj, &tc)))
       {
+        // The extra 2 bytes cover the comma and optional newline.
+        Buffer_Reserve (enc, enc->indent * (enc->level + 1) + 2);
+
         if(res < 0)
         {
           enc->iterEnd(obj, &tc);
@@ -711,9 +756,6 @@ static void encode(JSOBJ obj, JSONObjectEncoder *enc, const char *name, size_t c
         if (count > 0)
         {
           Buffer_AppendCharUnchecked (enc, ',');
-#ifndef JSON_NO_EXTRA_WHITESPACE
-          Buffer_AppendCharUnchecked (enc, ' ');
-#endif
         }
         Buffer_AppendIndentNewlineUnchecked (enc);
 
@@ -736,9 +778,11 @@ static void encode(JSOBJ obj, JSONObjectEncoder *enc, const char *name, size_t c
       enc->iterEnd(obj, &tc);
 
       if (count > 0) {
+        Buffer_Reserve (enc, enc->indent * enc->level + 1);
         Buffer_AppendIndentNewlineUnchecked (enc);
         Buffer_AppendIndentUnchecked (enc, enc->level);
       }
+      Buffer_Reserve (enc, 1);
       Buffer_AppendCharUnchecked (enc, '}');
       break;
     }
@@ -843,7 +887,7 @@ static void encode(JSOBJ obj, JSONObjectEncoder *enc, const char *name, size_t c
         return;
       }
 
-      Buffer_Reserve(enc, RESERVE_STRING(szlen));
+      Buffer_Reserve(enc, szlen);
       if (enc->errorMsg)
       {
         enc->endTypeContext(obj, &tc);


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index f185a450..11919ec7 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -31,10 +31,18 @@ jobs:
           python -m pip install -U pip
           python -m pip install -U pytest
           python -m pip install .
+        env:
+          CFLAGS: '-DDEBUG'
 
       - name: Tests
         run: |
-          pytest
+          python -m pytest --capture=no
+
+      - name: Test without debug mode
+        run: |
+          git clean -Xfd
+          python -m pip install --force-reinstall .
+          python -m pytest
 
       - name: Test with coverage
         if: ${{ startsWith(matrix.os, 'ubuntu') && matrix.python-version == '3.9' }}
diff --git a/tests/334-reproducer.json b/tests/334-reproducer.json
new file mode 100644
index 00000000..b53fcd5c
--- /dev/null
+++ b/tests/334-reproducer.json
@@ -0,0 +1,857 @@
+{
+	"ak.somestring.internal.Shadow": {
+		"id": 33300002,
+		"init_state": "(bk.action.array.Make, (bk.action.i32.Const, 0))",
+		"child": {
+			"ak.somestring.Flexbox": {
+				"flex_direction": "column",
+				"align_items": "stretch",
+				"children": [
+					{
+						"ak.somestring.Collection": {
+							"id": 33300001,
+							"snap": "center",
+							"direction": "row",
+							"children": [
+								{
+									"ak.somestring.Flexbox": {
+										"decoration": {
+											"ak.somestring.BoxDecoration": {
+												"background": {
+													"ak.somestring.ColorDrawable": {
+														"color": "#2c8932"
+													}
+												}
+											}
+										},
+										"children": [
+											{
+												"ak.somestring.Flexbox": {
+													"flex_direction": "column",
+													"align_items": "stretch",
+													"children": [
+														{
+															"ak.somestring.Flexbox": {
+																"flex_direction": "column",
+																"align_items": "stretch",
+																"children": [
+																	{
+																		"ak.somestring.Flexbox": {
+																			"children": [
+																				{
+																					"ls.components.Image": {
+																						"media_id": "10156403921218138",
+																						"preview_url": "https://scontent.xx.whoaa.net/v/t1.0-9/51099660_10156403921233138_3677795704043995136_n.jpg?_nc_cat=102&_nc_log=1&_nc_oc=AQk3Td-w9KpopLL2N1jgZ4WDMuxUyuGY3ZvY4mDSCk8W9-GjsFPi2S4gVQk0Y3A5ZaaQf7ASvQ2s_eR85kTmFvr0&_nc_ad=z-m&_nc_cid=0&_nc_zor=9&_nc_ht=scontent.xx&oh=fb16b0d60b13817a505f583cc9dad1eb&oe=5CBCDB46",
+																						"height": 278,
+																						"width": 156
+																					}
+																				}
+																			],
+																			"_style": {
+																				"flex": {
+																					"grow": 1
+																				}
+																			}
+																		}
+																	},
+																	{
+																		"ak.somestring.Flexbox": {
+																			"flex_direction": "column",
+																			"align_items": "stretch",
+																			"children": [
+																				{
+																					"ak.somestring.Flexbox": {
+																						"flex_direction": "row",
+																						"align_items": "stretch",
+																						"children": [
+																							{
+																								"ak.somestring.Flexbox": {
+																									"decoration": {
+																										"ak.somestring.BoxDecoration": {
+																											"background": {
+																												"ak.somestring.ColorDrawable": {
+																													"color": "#ffffff"
+																												}
+																											}
+																										}
+																									},
+																									"_style": {
+																										"flex": {
+																											"margin_right": "4dp",
+																											"grow": 1
+																										}
+																									}
+																								}
+																							},
+																							{
+																								"ak.somestring.Flexbox": {
+																									"decoration": {
+																										"ak.somestring.BoxDecoration": {
+																											"background": {
+																												"ak.somestring.ColorDrawable": {
+																													"color": "#ffffff"
+																												}
+																											}
+																										}
+																									},
+																									"_style": {
+																										"flex": {
+																											"margin_right": "4dp",
+																											"grow": 1
+																										}
+																									}
+																								}
+																							},
+																							{
+																								"ak.somestring.Flexbox": {
+																									"flex_direction": "row",
+																									"align_items": "stretch",
+																									"decoration": {
+																										"ak.somestring.BoxDecoration": {
+																											"background": {
+																												"ak.somestring.ColorDrawable": {
+																													"color": "#ffffff"
+																												}
+																											}
+																										}
+																									},
+																									"children": [
+																										{
+																											"ak.somestring.Flexbox": {
+																												"id": 33300004,
+																												"_style": {
+																													"flex": {
+																														"grow": 1
+																													}
+																												}
+																											}
+																										}
+																									],
+																									"_style": {
+																										"flex": {
+																											"margin_right": "4dp",
+																											"grow": 1
+																										}
+																									}
+																								}
+																							}
+																						],
+																						"_style": {
+																							"flex": {
+																								"height": "2dp",
+																								"margin_left": "4dp"
+																							}
+																						}
+																					}
+																				}
+																			],
+																			"_style": {
+																				"flex": {
+																					"position_type": "absolute",
+																					"left": "0dp",
+																					"top": "10dp",
+																					"margin_top": "10dp",
+																					"right": "0dp",
+																					"height": "2dp",
+																					"width": "100%"
+																				}
+																			}
+																		}
+																	}
+																],
+																"_style": {
+																	"flex": {
+																		"grow": 1
+																	}
+																}
+															}
+														},
+														{
+															"ak.somestring.Flexbox": {
+																"align_items": "flex_start",
+																"children": [
+																	{
+																		"ak.somestring.Flexbox": {
+																			"decoration": {
+																				"ak.somestring.BoxDecoration": {
+																					"corner_radius": "17dp"
+																				}
+																			},
+																			"children": [
+																				{
+																					"ls.components.Image": {
+																						"media_id": "10156403921218138",
+																						"preview_url": "https://scontent.xx.whoaa.net/v/t1.0-9/51099660_10156403921233138_3677795704043995136_n.jpg?_nc_cat=102&_nc_log=1&_nc_oc=AQk3Td-w9KpopLL2N1jgZ4WDMuxUyuGY3ZvY4mDSCk8W9-GjsFPi2S4gVQk0Y3A5ZaaQf7ASvQ2s_eR85kTmFvr0&_nc_ad=z-m&_nc_cid=0&_nc_zor=9&_nc_ht=scontent.xx&oh=fb16b0d60b13817a505f583cc9dad1eb&oe=5CBCDB46",
+																						"height": 34,
+																						"width": 34,
+																						"_style": {
+																							"flex": {
+																								"width": "34dp",
+																								"height": "34dp"
+																							}
+																						}
+																					}
+																				}
+																			],
+																			"_style": {
+																				"flex": {
+																					"margin_right": "12dp",
+																					"width": "34dp",
+																					"height": "34dp"
+																				}
+																			}
+																		}
+																	},
+																	{
+																		"ak.somestring.Flexbox": {
+																			"flex_direction": "column",
+																			"align_items": "flex_start",
+																			"children": [
+																				{
+																					"ak.somestring.RichText": {
+																						"children": [
+																							{
+																								"ak.somestring.TextSpan": {
+																									"text": "eric",
+																									"text_size": "15sp",
+																									"text_style": "bold",
+																									"text_color": "#ffffff"
+																								}
+																							}
+																						],
+																						"_style": {
+																							"flex": {
+																								"margin_bottom": "2dp",
+																								"width": "100%"
+																							}
+																						}
+																					}
+																				},
+																				{
+																					"ak.somestring.RichText": {
+																						"children": [
+																							{
+																								"ak.somestring.TextSpan": {
+																									"text": "8h",
+																									"text_size": "13sp",
+																									"text_style": "normal",
+																									"text_color": "#ffffff"
+																								}
+																							}
+																						],
+																						"_style": {
+																							"flex": {
+																								"width": "100%"
+																							}
+																						}
+																					}
+																				}
+																			],
+																			"_style": {
+																				"flex": {
+																					"width": "100%",
+																					"height": "100%"
+																				}
+																			}
+																		}
+																	}
+																],
+																"_style": {
+																	"flex": {
+																		"position_type": "absolute",
+																		"top": "30dp",
+																		"left": "10dp",
+																		"height": "48dp"
+																	}
+																}
+															}
+														},
+														{
+															"ak.somestring.Flexbox": {
+																"children": [
+																	{
+																		"ls.components.StoriesReplyBar": {}
+																	}
+																],
+																"_style": {
+																	"flex": {
+																		"width": "100%",
+																		"height": "45dp",
+																		"margin_top": "auto",
+																		"margin_bottom": "auto"
+																	}
+																}
+															}
+														}
+													],
+													"_style": {
+														"flex": {
+															"position_type": "absolute",
+															"width": "100%",
+															"height": "100%",
+															"grow": 1
+														}
+													}
+												}
+											},
+											{
+												"ak.somestring.Flexbox": {
+													"flex_direction": "column",
+													"align_items": "stretch",
+													"children": [
+														{
+															"ak.somestring.Flexbox": {
+																"flex_direction": "column",
+																"align_items": "stretch",
+																"children": [
+																	{
+																		"ak.somestring.Flexbox": {
+																			"children": [
+																				{
+																					"ls.components.Image": {
+																						"media_id": "10101230968216658",
+																						"preview_url": "https://scontent.xx.whoaa.net/v/t1.0-9/50800535_10101230968226638_6755212111762161664_n.jpg?_nc_cat=101&_nc_log=1&_nc_oc=AQmKcqYvt6DI7aeGk3k_oF6RHSVZkUg7f9hnBCWilyaOGdCWO0-u9_zssC5qGvca6wqsrz3AP0y1RPLPiZj8ycCv&_nc_ad=z-m&_nc_cid=0&_nc_zor=9&_nc_ht=scontent.xx&oh=2fffbab8f0a102d196454ee0138c1850&oe=5CC15206",
+																						"height": 278,
+																						"width": 156
+																					}
+																				}
+																			],
+																			"_style": {
+																				"flex": {
+																					"grow": 1
+																				}
+																			}
+																		}
+																	},
+																	{
+																		"ak.somestring.Flexbox": {
+																			"flex_direction": "column",
+																			"align_items": "stretch",
+																			"children": [
+																				{
+																					"ak.somestring.Flexbox": {
+																						"flex_direction": "row",
+																						"align_items": "stretch",
+																						"children": [
+																							{
+																								"ak.somestring.Flexbox": {
+																									"decoration": {
+																										"ak.somestring.BoxDecoration": {
+																											"background": {
+																												"ak.somestring.ColorDrawable": {
+																													"color": "#ffffff"
+																												}
+																											}
+																										}
+																									},
+																									"_style": {
+																										"flex": {
+																											"margin_right": "4dp",
+																											"grow": 1
+																										}
+																									}
+																								}
+																							},
+																							{
+																								"ak.somestring.Flexbox": {
+																									"flex_direction": "row",
+																									"align_items": "stretch",
+																									"decoration": {
+																										"ak.somestring.BoxDecoration": {
+																											"background": {
+																												"ak.somestring.ColorDrawable": {
+																													"color": "#ffffff"
+																												}
+																											}
+																										}
+																									},
+																									"children": [
+																										{
+																											"ak.somestring.Flexbox": {
+																												"id": 33300005,
+																												"_style": {
+																													"flex": {
+																														"grow": 1
+																													}
+																												}
+																											}
+																										}
+																									],
+																									"_style": {
+																										"flex": {
+																											"margin_right": "4dp",
+																											"grow": 1
+																										}
+																									}
+																								}
+																							},
+																							{
+																								"ak.somestring.Flexbox": {
+																									"decoration": {
+																										"ak.somestring.BoxDecoration": {
+																											"background": {
+																												"ak.somestring.ColorDrawable": {
+																													"color": "#cccccc"
+																												}
+																											}
+																										}
+																									},
+																									"_style": {
+																										"flex": {
+																											"margin_right": "4dp",
+																											"grow": 1
+																										}
+																									}
+																								}
+																							}
+																						],
+																						"_style": {
+																							"flex": {
+																								"height": "2dp",
+																								"margin_left": "4dp"
+																							}
+																						}
+																					}
+																				}
+																			],
+																			"_style": {
+																				"flex": {
+																					"position_type": "absolute",
+																					"left": "0dp",
+																					"top": "10dp",
+																					"margin_top": "10dp",
+																					"right": "0dp",
+																					"height": "2dp",
+																					"width": "100%"
+																				}
+																			}
+																		}
+																	}
+																],
+																"_style": {
+																	"flex": {
+																		"grow": 1
+																	}
+																}
+															}
+														},
+														{
+															"ak.somestring.Flexbox": {
+																"align_items": "flex_start",
+																"children": [
+																	{
+																		"ak.somestring.Flexbox": {
+																			"decoration": {
+																				"ak.somestring.BoxDecoration": {
+																					"corner_radius": "17dp"
+																				}
+																			},
+																			"children": [
+																				{
+																					"ls.components.Image": {
+																						"media_id": "10101230968216658",
+																						"preview_url": "https://scontent.xx.whoaa.net/v/t1.0-9/50800535_10101230968226638_6755212111762161664_n.jpg?_nc_cat=101&_nc_log=1&_nc_oc=AQmKcqYvt6DI7aeGk3k_oF6RHSVZkUg7f9hnBCWilyaOGdCWO0-u9_zssC5qGvca6wqsrz3AP0y1RPLPiZj8ycCv&_nc_ad=z-m&_nc_cid=0&_nc_zor=9&_nc_ht=scontent.xx&oh=2fffbab8f0a102d196454ee0138c1850&oe=5CC15206",
+																						"height": 34,
+																						"width": 34,
+																						"_style": {
+																							"flex": {
+																								"width": "34dp",
+																								"height": "34dp"
+																							}
+																						}
+																					}
+																				}
+																			],
+																			"_style": {
+																				"flex": {
+																					"margin_right": "12dp",
+																					"width": "34dp",
+																					"height": "34dp"
+																				}
+																			}
+																		}
+																	},
+																	{
+																		"ak.somestring.Flexbox": {
+																			"flex_direction": "column",
+																			"align_items": "flex_start",
+																			"children": [
+																				{
+																					"ak.somestring.RichText": {
+																						"children": [
+																							{
+																								"ak.somestring.TextSpan": {
+																									"text": "eric",
+																									"text_size": "15sp",
+																									"text_style": "bold",
+																									"text_color": "#ffffff"
+																								}
+																							}
+																						],
+																						"_style": {
+																							"flex": {
+																								"margin_bottom": "2dp",
+																								"width": "100%"
+																							}
+																						}
+																					}
+																				},
+																				{
+																					"ak.somestring.RichText": {
+																						"children": [
+																							{
+																								"ak.somestring.TextSpan": {
+																									"text": "2h",
+																									"text_size": "13sp",
+																									"text_style": "normal",
+																									"text_color": "#ffffff"
+																								}
+																							}
+																						],
+																						"_style": {
+																							"flex": {
+																								"width": "100%"
+																							}
+																						}
+																					}
+																				}
+																			],
+																			"_style": {
+																				"flex": {
+																					"width": "100%",
+																					"height": "100%"
+																				}
+																			}
+																		}
+																	}
+																],
+																"_style": {
+																	"flex": {
+																		"position_type": "absolute",
+																		"top": "30dp",
+																		"left": "10dp",
+																		"height": "48dp"
+																	}
+																}
+															}
+														},
+														{
+															"ak.somestring.Flexbox": {
+																"children": [
+																	{
+																		"ls.components.StoriesReplyBar": {}
+																	}
+																],
+																"_style": {
+																	"flex": {
+																		"width": "100%",
+																		"height": "45dp",
+																		"margin_top": "auto",
+																		"margin_bottom": "auto"
+																	}
+																}
+															}
+														}
+													],
+													"_style": {
+														"flex": {
+															"position_type": "absolute",
+															"width": "100%",
+															"height": "100%",
+															"grow": 1
+														}
+													}
+												}
+											},
+											{
+												"ak.somestring.Flexbox": {
+													"flex_direction": "column",
+													"align_items": "stretch",
+													"children": [
+														{
+															"ak.somestring.Flexbox": {
+																"flex_direction": "column",
+																"align_items": "stretch",
+																"children": [
+																	{
+																		"ak.somestring.Flexbox": {
+																			"children": [
+																				{
+																					"ls.components.Video": {
+																						"media_id": "10156395664922983",
+																						"video_url": "https://video.xx.whoaa.net/v/t42.9040-2/51636103_316525608877874_407931582842667008_n.mp4?_nc_cat=109&efg=eyJ2ZW5jb2RlX3RhZyI6InN2ZV9oZCJ9&_nc_log=1&_nc_oc=AQm6aMctRAFdMe3C66upF2JulQP4mV3Hd4THkueZex952PR389F6Ay9XHm1S40dV1x7M1I-fAW5y3iH7JlQ3MgDM&_nc_ht=video.xx&oh=e17b1f7ec67619d57a5b1cda5e076fef&oe=5C587F7D",
+																						"preview_url": "https://scontent.xx.whoaa.net/v/t15.5256-10/s960x960/51767715_10156395667952983_4168426706077483008_n.jpg?_nc_cat=104&_nc_log=1&_nc_oc=AQnVwEZk2vG8Q3TcoR0SxdXSi8rL_GaST2aH3i9auDcDnJNTRKvuYEFfd_qKGBhmD4-bo-f8BY5j9jHyit765O7P&_nc_ad=z-m&_nc_cid=0&_nc_zor=9&_nc_ht=scontent.xx&oh=9a17e4bcf8a2a9aabc21d2ecf9f8611b&oe=5CB3D14B",
+																						"show_media_play_button": false,
+																						"media_height": 960,
+																						"media_width": 540
+																					}
+																				}
+																			],
+																			"_style": {
+																				"flex": {
+																					"grow": 1
+																				}
+																			}
+																		}
+																	},
+																	{
+																		"ak.somestring.Flexbox": {
+																			"flex_direction": "column",
+																			"align_items": "stretch",
+																			"children": [
+																				{
+																					"ak.somestring.Flexbox": {
+																						"flex_direction": "row",
+																						"align_items": "stretch",
+																						"children": [
+																							{
+																								"ak.somestring.Flexbox": {
+																									"flex_direction": "row",
+																									"align_items": "stretch",
+																									"decoration": {
+																										"ak.somestring.BoxDecoration": {
+																											"background": {
+																												"ak.somestring.ColorDrawable": {
+																													"color": "#ffffff"
+																												}
+																											}
+																										}
+																									},
+																									"children": [
+																										{
+																											"ak.somestring.Flexbox": {
+																												"id": 33300006,
+																												"_style": {
+																													"flex": {
+																														"grow": 1
+																													}
+																												}
+																											}
+																										}
+																									],
+																									"_style": {
+																										"flex": {
+																											"margin_right": "4dp",
+																											"grow": 1
+																										}
+																									}
+																								}
+																							},
+																							{
+																								"ak.somestring.Flexbox": {
+																									"decoration": {
+																										"ak.somestring.BoxDecoration": {
+																											"background": {
+																												"ak.somestring.ColorDrawable": {
+																													"color": "#cccccc"
+																												}
+																											}
+																										}
+																									},
+																									"_style": {
+																										"flex": {
+																											"margin_right": "4dp",
+																											"grow": 1
+																										}
+																									}
+																								}
+																							},
+																							{
+																								"ak.somestring.Flexbox": {
+																									"decoration": {
+																										"ak.somestring.BoxDecoration": {
+																											"background": {
+																												"ak.somestring.ColorDrawable": {
+																													"color": "#cccccc"
+																												}
+																											}
+																										}
+																									},
+																									"_style": {
+																										"flex": {
+																											"margin_right": "4dp",
+																											"grow": 1
+																										}
+																									}
+																								}
+																							}
+																						],
+																						"_style": {
+																							"flex": {
+																								"height": "2dp",
+																								"margin_left": "4dp"
+																							}
+																						}
+																					}
+																				}
+																			],
+																			"_style": {
+																				"flex": {
+																					"position_type": "absolute",
+																					"left": "0dp",
+																					"top": "10dp",
+																					"margin_top": "10dp",
+																					"right": "0dp",
+																					"height": "2dp",
+																					"width": "100%"
+																				}
+																			}
+																		}
+																	}
+																],
+																"_style": {
+																	"flex": {
+																		"grow": 1
+																	}
+																}
+															}
+														},
+														{
+															"ak.somestring.Flexbox": {
+																"align_items": "flex_start",
+																"children": [
+																	{
+																		"ak.somestring.Flexbox": {
+																			"decoration": {
+																				"ak.somestring.BoxDecoration": {
+																					"corner_radius": "17dp"
+																				}
+																			},
+																			"children": [
+																				{
+																					"ls.components.Image": {
+																						"media_id": "10156395664922983",
+																						"height": 34,
+																						"width": 34,
+																						"_style": {
+																							"flex": {
+																								"width": "34dp",
+																								"height": "34dp"
+																							}
+																						}
+																					}
+																				}
+																			],
+																			"_style": {
+																				"flex": {
+																					"margin_right": "12dp",
+																					"width": "34dp",
+																					"height": "34dp"
+																				}
+																			}
+																		}
+																	},
+																	{
+																		"ak.somestring.Flexbox": {
+																			"flex_direction": "column",
+																			"align_items": "flex_start",
+																			"children": [
+																				{
+																					"ak.somestring.RichText": {
+																						"children": [
+																							{
+																								"ak.somestring.TextSpan": {
+																									"text": "eric",
+																									"text_size": "15sp",
+																									"text_style": "bold",
+																									"text_color": "#ffffff"
+																								}
+																							}
+																						],
+																						"_style": {
+																							"flex": {
+																								"margin_bottom": "2dp",
+																								"width": "100%"
+																							}
+																						}
+																					}
+																				},
+																				{
+																					"ak.somestring.RichText": {
+																						"children": [
+																							{
+																								"ak.somestring.TextSpan": {
+																									"text": "20h",
+																									"text_size": "13sp",
+																									"text_style": "normal",
+																									"text_color": "#ffffff"
+																								}
+																							}
+																						],
+																						"_style": {
+																							"flex": {
+																								"width": "100%"
+																							}
+																						}
+																					}
+																				}
+																			],
+																			"_style": {
+																				"flex": {
+																					"width": "100%",
+																					"height": "100%"
+																				}
+																			}
+																		}
+																	}
+																],
+																"_style": {
+																	"flex": {
+																		"position_type": "absolute",
+																		"top": "30dp",
+																		"left": "10dp",
+																		"height": "48dp"
+																	}
+																}
+															}
+														},
+														{
+															"ak.somestring.Flexbox": {
+																"children": [
+																	{
+																		"ls.components.StoriesReplyBar": {}
+																	}
+																],
+																"_style": {
+																	"flex": {
+																		"width": "100%",
+																		"height": "45dp",
+																		"margin_top": "auto",
+																		"margin_bottom": "auto"
+																	}
+																}
+															}
+														}
+													],
+													"_style": {
+														"flex": {
+															"position_type": "absolute",
+															"width": "100%",
+															"height": "100%",
+															"grow": 1
+														}
+													}
+												}
+											}
+										],
+										"_style": {
+											"flex": {
+												"width": "100%",
+												"height": "100%"
+											}
+										}
+									}
+								}
+							],
+							"_style": {
+								"flex": {
+									"height": "100%"
+								}
+							}
+						}
+					}
+				]
+			}
+		}
+	}
+}
diff --git a/tests/fuzz.py b/tests/fuzz.py
new file mode 100644
index 00000000..043ce0f1
--- /dev/null
+++ b/tests/fuzz.py
@@ -0,0 +1,169 @@
+"""
+A brute force fuzzer for detecting memory issues in ujson.dumps(). To use, first
+compile ujson in debug mode:
+
+    CFLAGS='-DDEBUG' python setup.py -q build_ext --inplace -f
+
+Then run without arguments:
+
+    python tests/fuzz.py
+
+If it crashes, the last line of output is the arguments to reproduce the
+failure.
+
+    python tests/fuzz.py {{ last line of output before crash }}
+
+Adding --dump-python or --dump-json will print the object it intends to
+serialise as either a Python literal or in JSON.
+
+"""
+
+import argparse
+import itertools
+import json
+import math
+import random
+import re
+from pprint import pprint
+
+import ujson
+
+
+class FuzzGenerator:
+    """A random JSON serialisable object generator."""
+
+    def __init__(self, seed=None):
+        self._randomizer = random.Random(seed)
+        self._shrink = 1
+
+    def key(self):
+        key_types = [self.int, self.float, self.string, self.null, self.bool]
+        return self._randomizer.choice(key_types)()
+
+    def item(self):
+        if self._randomizer.random() > 0.8:
+            return self.key()
+        return self._randomizer.choice([self.list, self.dict])()
+
+    def int(self):
+        return int(self.float())
+
+    def float(self):
+        sign = self._randomizer.choice([-1, 1, 0])
+        return sign * math.exp(self._randomizer.uniform(-40, 40))
+
+    def string(self):
+        characters = ["\x00", "\t", "a", "\U0001f680", "<></>", "\u1234"]
+        return self._randomizer.choice(characters) * self.length()
+
+    def bool(self):
+        return self._randomizer.random() < 0.5
+
+    def null(self):
+        return None
+
+    def list(self):
+        return [self.item() for i in range(self.length())]
+
+    def dict(self):
+        return {self.key(): self.item() for i in range(self.length())}
+
+    def length(self):
+        self._shrink *= 0.99
+        return int(math.exp(self._randomizer.uniform(-0.5, 5)) * self._shrink)
+
+
+def random_object(seed=None):
+    return FuzzGenerator(seed).item()
+
+
+class RangeOption(argparse.Action):
+    def __call__(self, parser, namespace, values, option_string=None):
+        values = re.findall("[^: ]+", values)
+        if len(values) == 1:
+            values = (int(values[0]),)
+        else:
+            values = range(*map(int, values))
+        setattr(namespace, self.dest, values)
+
+
+class ListOption(argparse.Action):
+    def __call__(self, parser, namespace, values, option_string=None):
+        values = tuple(map(int, re.findall("[^, ]+", values)))
+        setattr(namespace, self.dest, values)
+
+
+parser = argparse.ArgumentParser(
+    epilog=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
+)
+parser.add_argument(
+    "--seed",
+    default=range(100),
+    action=RangeOption,
+    dest="seeds",
+    help="A seed or range of seeds (in the form start:end[:step]) "
+    "to initialise the randomizer.",
+)
+parser.add_argument(
+    "--indent",
+    default=(0, 1, 2, 3, 4, 5, 12, 100, 1000),
+    action=ListOption,
+    help="A comma separated sequence of indentation lengths to test.",
+)
+parser.add_argument(
+    "--ensure_ascii",
+    default=(0, 1),
+    action=ListOption,
+    help="Sets the ensure_ascii option to ujson.dumps(). "
+    "May be 0 or 1 or 0,1 to testboth.",
+)
+parser.add_argument(
+    "--encode_html_chars",
+    default=(0, 1),
+    action=ListOption,
+    help="Sets the encode_html_chars option to ujson.dumps(). "
+    "May be 0 or 1 or 0,1 to test both.",
+)
+parser.add_argument(
+    "--escape_forward_slashes",
+    default=(0, 1),
+    action=ListOption,
+    help="Sets the escape_forward_slashes option to ujson.dumps(). "
+    "May be 0 or 1 or 0,1 to test both.",
+)
+parser.add_argument(
+    "--dump-python",
+    action="store_true",
+    help="Print the randomly generated object as a Python literal and exit.",
+)
+parser.add_argument(
+    "--dump-json",
+    action="store_true",
+    help="Print the randomly generated object in JSON format and exit.",
+)
+
+
+def cli(args=None):
+    options = dict(parser.parse_args(args)._get_kwargs())
+    if options.pop("dump_json"):
+        print(json.dumps(random_object(options["seeds"][0]), indent=2))
+    elif options.pop("dump_python"):
+        pprint(random_object(options["seeds"][0]))
+    else:
+        fuzz(**options)
+
+
+def fuzz(seeds, **options):
+    try:
+        for seed in seeds:
+            data = random_object(seed)
+            for permutation in itertools.product(*options.values()):
+                _options = dict(zip(options.keys(), permutation))
+                print(f"--seed {seed}", *(f"--{k} {v}" for (k, v) in _options.items()))
+                ujson.dumps(data, **_options)
+    except KeyboardInterrupt:
+        pass
+
+
+if __name__ == "__main__":
+    cli()
diff --git a/tests/test_ujson.py b/tests/test_ujson.py
index 56e7664b..b0b39479 100644
--- a/tests/test_ujson.py
+++ b/tests/test_ujson.py
@@ -7,6 +7,7 @@
 import sys
 import uuid
 from collections import OrderedDict
+from pathlib import Path
 
 import pytest
 import ujson
@@ -947,6 +948,32 @@ def default(value):
         ujson.dumps(unjsonable_obj, default=default)
 
 
+@pytest.mark.parametrize("indent", list(range(65537, 65542)))
+def test_dump_huge_indent(indent):
+    ujson.encode({"a": True}, indent=indent)
+
+
+@pytest.mark.parametrize("first_length", list(range(2, 7)))
+@pytest.mark.parametrize("second_length", list(range(10919, 10924)))
+def test_dump_long_string(first_length, second_length):
+    ujson.dumps(["a" * first_length, "\x00" * second_length])
+
+
+def test_dump_indented_nested_list():
+    a = _a = []
+    for i in range(20):
+        _a.append(list(range(i)))
+        _a = _a[-1]
+        ujson.dumps(a, indent=i)
+
+
+@pytest.mark.parametrize("indent", [0, 1, 2, 4, 5, 8, 49])
+def test_issue_334(indent):
+    path = Path(__file__).with_name("334-reproducer.json")
+    a = ujson.loads(path.read_bytes())
+    ujson.dumps(a, indent=indent)
+
+
 """
 def test_decode_numeric_int_frc_overflow():
 input = "X.Y"