diff --git a/changes/bug24150 b/changes/bug24150
new file mode 100644
index 0000000000..cfda7c40da
--- /dev/null
+++ b/changes/bug24150
@@ -0,0 +1,4 @@
+ o Minor bugfixes (v3 onion services):
+ - Fix a memory leak when decrypting a badly formatted v3 onion
+ service descriptor. Fixes bug 24150; bugfix on 0.3.2.1-alpha.
+ Found by OSS-Fuzz; this is OSS-Fuzz issue 3994.
diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c
index a8ff3471c7..1708866944 100644
--- a/src/or/hs_descriptor.c
+++ b/src/or/hs_descriptor.c
@@ -1302,7 +1302,11 @@ encrypted_data_length_is_valid(size_t len)
* encrypted_blob_size. Use the descriptor object desc to
* generate the right decryption keys; set decrypted_out to the
* plaintext. If is_superencrypted_layer is set, this is the outter
- * encrypted layer of the descriptor. */
+ * encrypted layer of the descriptor.
+ *
+ * On any error case, including an empty output, return 0 and set
+ * *decrypted_out to NULL.
+ */
MOCK_IMPL(STATIC size_t,
decrypt_desc_layer,(const hs_descriptor_t *desc,
const uint8_t *encrypted_blob,
@@ -1382,6 +1386,11 @@ decrypt_desc_layer,(const hs_descriptor_t *desc,
}
}
+ if (result_len == 0) {
+ /* Treat this as an error, so that somebody will free the output. */
+ goto err;
+ }
+
/* Make sure to NUL terminate the string. */
decrypted[encrypted_len] = '\0';
*decrypted_out = (char *) decrypted;
diff --git a/src/test/fuzz/fuzz_hsdescv3.c b/src/test/fuzz/fuzz_hsdescv3.c
index 30e82c9252..428774e330 100644
--- a/src/test/fuzz/fuzz_hsdescv3.c
+++ b/src/test/fuzz/fuzz_hsdescv3.c
@@ -50,7 +50,13 @@ mock_decrypt_desc_layer(const hs_descriptor_t *desc,
*decrypted_out = tor_memdup_nulterm(
encrypted_blob + HS_DESC_ENCRYPTED_SALT_LEN,
encrypted_blob_size - overhead);
- return strlen(*decrypted_out);
+ size_t result = strlen(*decrypted_out);
+ if (result) {
+ return result;
+ } else {
+ tor_free(*decrypted_out);
+ return 0;
+ }
}
int