Index: common/stdc/uchar.c
===================================================================
--- common/stdc/uchar.c	(revision 11782da4004838b0899c80fba1efa878b8cab87e)
+++ common/stdc/uchar.c	(revision 06009762b7fb89c5261e22ebc8a890bf71867b44)
@@ -84,4 +84,10 @@
 }
 
+static bool _is_non_shortest(unsigned short cont, uint8_t b)
+{
+	return (cont == 0b1111110000000000 && !(b & 0b00100000)) ||
+	    (cont == 0b1111111111110000 && !(b & 0b00110000));
+}
+
 size_t mbrtoc32(char32_t *c, const char *s, size_t n, mbstate_t *mb)
 {
@@ -139,4 +145,10 @@
 
 		if (_is_2_byte(b)) {
+			/* Reject non-shortest form. */
+			if (!(b & 0b00011110)) {
+				_set_ilseq();
+				return UCHAR_ILSEQ;
+			}
+
 			/* 2 byte encoding               110xxxxx */
 			mb->continuation = b ^ 0b0000000011000000;
@@ -152,8 +164,9 @@
 	}
 
-	while (i < n) {
+	for (; i < n; i++) {
 		/* Read continuation bytes. */
-
-		if (!_is_continuation(s[i])) {
+		uint8_t b = s[i];
+
+		if (!_is_continuation(b) || _is_non_shortest(mb->continuation, b)) {
 			_set_ilseq();
 			return UCHAR_ILSEQ;
@@ -162,10 +175,10 @@
 		/* Top bit becomes zero just before the last byte is shifted in. */
 		if (!(mb->continuation & 0x8000)) {
-			*c = ((char32_t) mb->continuation) << 6 | (s[i++] & 0x3f);
+			*c = ((char32_t) mb->continuation) << 6 | (b & 0x3f);
 			mb->continuation = 0;
-			return i;
-		}
-
-		mb->continuation = mb->continuation << 6 | (s[i++] & 0x3f);
+			return ++i;
+		}
+
+		mb->continuation = mb->continuation << 6 | (b & 0x3f);
 	}
 
Index: common/str.c
===================================================================
--- common/str.c	(revision 11782da4004838b0899c80fba1efa878b8cab87e)
+++ common/str.c	(revision 06009762b7fb89c5261e22ebc8a890bf71867b44)
@@ -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;
 }
Index: uspace/lib/c/test/str.c
===================================================================
--- uspace/lib/c/test/str.c	(revision 11782da4004838b0899c80fba1efa878b8cab87e)
+++ uspace/lib/c/test/str.c	(revision 06009762b7fb89c5261e22ebc8a890bf71867b44)
@@ -27,4 +27,5 @@
  */
 
+#include "pcut/asserts.h"
 #include <stdio.h>
 #include <str.h>
@@ -115,3 +116,42 @@
 }
 
+PCUT_TEST(str_non_shortest)
+{
+	/* Overlong zero. */
+	const char overlong1[] = { 0b11000000, 0b10000000, 0 };
+	const char overlong2[] = { 0b11100000, 0b10000000, 0 };
+	const char overlong3[] = { 0b11110000, 0b10000000, 0 };
+
+	const char overlong4[] = { 0b11000001, 0b10111111, 0 };
+	const char overlong5[] = { 0b11100000, 0b10011111, 0b10111111, 0 };
+	const char overlong6[] = { 0b11110000, 0b10001111, 0b10111111, 0b10111111, 0 };
+
+	size_t offset = 0;
+	PCUT_ASSERT_INT_EQUALS(U_SPECIAL, str_decode(overlong1, &offset, sizeof(overlong1)));
+	offset = 0;
+	PCUT_ASSERT_INT_EQUALS(U_SPECIAL, str_decode(overlong2, &offset, sizeof(overlong2)));
+	offset = 0;
+	PCUT_ASSERT_INT_EQUALS(U_SPECIAL, str_decode(overlong3, &offset, sizeof(overlong3)));
+	offset = 0;
+	PCUT_ASSERT_INT_EQUALS(U_SPECIAL, str_decode(overlong4, &offset, sizeof(overlong4)));
+	offset = 0;
+	PCUT_ASSERT_INT_EQUALS(U_SPECIAL, str_decode(overlong5, &offset, sizeof(overlong5)));
+	offset = 0;
+	PCUT_ASSERT_INT_EQUALS(U_SPECIAL, str_decode(overlong6, &offset, sizeof(overlong6)));
+
+	char sanitized[sizeof(overlong6)];
+	str_cpy(sanitized, STR_NO_LIMIT, overlong1);
+	PCUT_ASSERT_INT_EQUALS(U_SPECIAL, sanitized[0]);
+	str_cpy(sanitized, STR_NO_LIMIT, overlong2);
+	PCUT_ASSERT_INT_EQUALS(U_SPECIAL, sanitized[0]);
+	str_cpy(sanitized, STR_NO_LIMIT, overlong3);
+	PCUT_ASSERT_INT_EQUALS(U_SPECIAL, sanitized[0]);
+	str_cpy(sanitized, STR_NO_LIMIT, overlong4);
+	PCUT_ASSERT_INT_EQUALS(U_SPECIAL, sanitized[0]);
+	str_cpy(sanitized, STR_NO_LIMIT, overlong5);
+	PCUT_ASSERT_INT_EQUALS(U_SPECIAL, sanitized[0]);
+	str_cpy(sanitized, STR_NO_LIMIT, overlong6);
+	PCUT_ASSERT_INT_EQUALS(U_SPECIAL, sanitized[0]);
+}
+
 PCUT_EXPORT(str);
