Index: common/str.c
===================================================================
--- common/str.c	(revision 6120b7ba197ea476787bce9a6b086bd533f93606)
+++ common/str.c	(revision f94a11f41744187ab341186d49bcbcc8e6ad5718)
@@ -210,5 +210,5 @@
 char32_t str_decode(const char *str, size_t *offset, size_t size)
 {
-	if (*offset + 1 > size)
+	if (*offset >= size)
 		return 0;
 
@@ -235,5 +235,5 @@
 
 	/* Decode continuation bytes */
-	while (cbytes > 0) {
+	for (int i = 0; i < cbytes; i++) {
 		uint8_t b = (uint8_t) str[*offset];
 
@@ -245,6 +245,12 @@
 		/* Shift data bits to ch */
 		ch = (ch << CONT_BITS) | (char32_t) (b & LO_MASK_8(CONT_BITS));
-		cbytes--;
-	}
+	}
+
+	/*
+	 * Reject non-shortest form encodings.
+	 * See https://www.unicode.org/versions/corrigendum1.html
+	 */
+	if (cbytes != _char_continuation_bytes(ch))
+		return U_SPECIAL;
 
 	return ch;
@@ -350,21 +356,50 @@
 
 /* Convert in place any bytes that don't form a valid character into U_SPECIAL. */
-static void _repair_string(char *str, size_t n)
-{
-	for (; *str && n > 0; str++, n--) {
-		int cont = _continuation_bytes(*str);
-		if (cont == 0)
+static void _sanitize_string(char *str, size_t n)
+{
+	uint8_t *b = (uint8_t *) str;
+
+	for (; *b && n > 0; b++, n--) {
+		int cont = _continuation_bytes(b[0]);
+		if (__builtin_expect(cont, 0) == 0)
 			continue;
 
 		if (cont < 0 || n <= (size_t) cont) {
-			*str = U_SPECIAL;
+			b[0] = U_SPECIAL;
 			continue;
 		}
 
+		/* Check continuation bytes. */
 		for (int i = 1; i <= cont; i++) {
-			if (!_is_continuation_byte(str[i])) {
-				*str = U_SPECIAL;
+			if (!_is_continuation_byte(b[i])) {
+				b[0] = U_SPECIAL;
 				continue;
 			}
+		}
+
+		/*
+		 * Check for non-shortest form encoding.
+		 * See https://www.unicode.org/versions/corrigendum1.html
+		 */
+
+		switch (cont) {
+		case 1:
+			/* 0b110!!!!x 0b10xxxxxx */
+			if (!(b[0] & 0b00011110))
+				b[0] = U_SPECIAL;
+
+			continue;
+		case 2:
+			/* 0b1110!!!! 0b10!xxxxx 0b10xxxxxx */
+			if (!(b[0] & 0b00001111) && !(b[1] & 0b00100000))
+				b[0] = U_SPECIAL;
+
+			continue;
+		case 3:
+			/* 0b11110!!! 0b10!!xxxx 0b10xxxxxx 0b10xxxxxx */
+			if (!(b[0] & 0b00000111) && !(b[1] & 0b00110000))
+				b[0] = U_SPECIAL;
+
+			continue;
 		}
 	}
@@ -886,5 +921,14 @@
 static void _str_cpyn(char *dest, size_t size, const char *src)
 {
+	assert(dest && src && size);
+
+	if (!dest || !src || !size)
+		return;
+
+	if (size == STR_NO_LIMIT)
+		return _str_cpy(dest, src);
+
 	char *dest_top = dest + size - 1;
+	assert(size == 1 || dest < dest_top);
 
 	while (*src && dest < dest_top)
@@ -912,4 +956,5 @@
 	assert(src != NULL);
 	assert(dest != NULL);
+	assert(size == STR_NO_LIMIT || dest + size > dest);
 
 	/* Copy data. */
@@ -917,5 +962,5 @@
 
 	/* In-place translate invalid bytes to U_SPECIAL. */
-	_repair_string(dest, size);
+	_sanitize_string(dest, size);
 }
 
@@ -946,5 +991,5 @@
 
 	/* In-place translate invalid bytes to U_SPECIAL. */
-	_repair_string(dest, size);
+	_sanitize_string(dest, size);
 }
 
@@ -965,8 +1010,11 @@
 	assert(dest != NULL);
 	assert(size > 0);
+	assert(size == STR_NO_LIMIT || dest + size > dest);
 
 	size_t dstr_size = _str_nsize(dest, size);
-	_str_cpyn(dest + dstr_size, size - dstr_size, src);
-	_repair_string(dest + dstr_size, size - dstr_size);
+	if (dstr_size < size) {
+		_str_cpyn(dest + dstr_size, size - dstr_size, src);
+		_sanitize_string(dest + dstr_size, size - dstr_size);
+	}
 }
 
@@ -1545,6 +1593,6 @@
 		return NULL;
 
-	_str_cpy(dest, src);
-	_repair_string(dest, size);
+	memcpy(dest, src, size);
+	_sanitize_string(dest, size);
 	return dest;
 }
@@ -1572,12 +1620,13 @@
 char *str_ndup(const char *src, size_t n)
 {
-	size_t size = _str_nsize(src, n) + 1;
-
-	char *dest = malloc(size);
+	size_t size = _str_nsize(src, n);
+
+	char *dest = malloc(size + 1);
 	if (!dest)
 		return NULL;
 
-	_str_cpyn(dest, size, src);
-	_repair_string(dest, size);
+	memcpy(dest, src, size);
+	_sanitize_string(dest, size);
+	dest[size] = 0;
 	return dest;
 }
