[PATCH] befs: check NULL return from befs_bt_get_key() and fix keysize in befs_btree_read()

Hui Peng posted 1 patch 4 days, 22 hours ago
[PATCH] befs: check NULL return from befs_bt_get_key() and fix keysize in befs_btree_read()
Posted by Hui Peng 4 days, 22 hours ago
Fix two issues in fs/befs/:

1. In befs_btree_find() and befs_btree_read() (fs/befs/btree.c), check
   for a NULL return from befs_bt_get_key() when a corrupted B+tree node
   has out-of-bounds key offsets or lengths.
2. In befs_btree_read() and befs_readdir() (fs/befs/linuxvfs.c), set
   keysize from the actual copied string length rather than the raw
   buffer size so uninitialized stack bytes are not passed to filldir().

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
diff --git a/fs/befs/btree.c b/fs/befs/btree.c
index aa24f1daccdd..67633e5f27fe 100644
--- a/fs/befs/btree.c
+++ b/fs/befs/btree.c
@@ -219,6 +219,19 @@ befs_bt_read_node(struct super_block *sb, const befs_data_stream *ds,
 	node->head.all_key_length =
 	    fs16_to_cpu(sb, node->od_node->all_key_length);
 
+	{
+		size_t keylen_off = round_up(sizeof(befs_btree_nodehead) +
+					     node->head.all_key_length, 8);
+		size_t total_needed = keylen_off +
+			(size_t)node->head.all_key_count * (sizeof(fs16) + sizeof(fs64));
+		if (off >= node->bh->b_size ||
+		    total_needed > node->bh->b_size - off) {
+			brelse(node->bh);
+			node->bh = NULL;
+			return BEFS_ERR;
+		}
+	}
+
 	befs_debug(sb, "<--- %s", __func__);
 	return BEFS_OK;
 }
@@ -343,6 +356,8 @@ befs_find_key(struct super_block *sb, struct befs_btree_node *node,
 	/* if node can not contain key, just skip this node */
 	last = node->head.all_key_count - 1;
 	thiskey = befs_bt_get_key(sb, node, last, &keylen);
+	if (!thiskey)
+		return BEFS_BT_NOT_FOUND;
 
 	eq = befs_compare_strings(thiskey, keylen, findkey, findkey_len);
 	if (eq < 0) {
@@ -360,6 +375,8 @@ befs_find_key(struct super_block *sb, struct befs_btree_node *node,
 		befs_debug(sb, "first: %d, last: %d, mid: %d", first, last,
 			   mid);
 		thiskey = befs_bt_get_key(sb, node, mid, &keylen);
+		if (!thiskey)
+			return BEFS_BT_NOT_FOUND;
 		eq = befs_compare_strings(thiskey, keylen, findkey,
 					  findkey_len);
 
@@ -417,6 +434,7 @@ befs_btree_read(struct super_block *sb, const befs_data_stream *ds,
 	int cur_key;
 	fs64 *valarray;
 	char *keystart;
+	ssize_t copy_len;
 	u16 keylen;
 	int res;
 
@@ -487,6 +505,10 @@ befs_btree_read(struct super_block *sb, const befs_data_stream *ds,
 	valarray = befs_bt_valarray(this_node);
 
 	keystart = befs_bt_get_key(sb, this_node, cur_key, &keylen);
+	if (!keystart) {
+		brelse(this_node->bh);
+		goto error_alloc;
+	}
 
 	befs_debug(sb, "Read [%llu,%d]: keysize %d",
 		   (long long unsigned int)node_off, (int)cur_key,
@@ -499,9 +521,9 @@ befs_btree_read(struct super_block *sb, const befs_data_stream *ds,
 		goto error_alloc;
 	}
 
-	strscpy(keybuf, keystart, keylen + 1);
+	copy_len = strscpy(keybuf, keystart, keylen + 1);
+	*keysize = (copy_len < 0) ? keylen : copy_len;
 	*value = fs64_to_cpu(sb, valarray[cur_key]);
-	*keysize = keylen;
 
 	befs_debug(sb, "Read [%llu,%d]: Key \"%.*s\", Value %llu", node_off,
 		   cur_key, keylen, keybuf, *value);
@@ -678,7 +700,7 @@ befs_bt_get_key(struct super_block *sb, struct befs_btree_node *node,
 	char *keystart;
 	fs16 *keylen_index;
 
-	if (index < 0 || index > node->head.all_key_count) {
+	if (index < 0 || index >= node->head.all_key_count) {
 		*keylen = 0;
 		return NULL;
 	}
@@ -691,6 +713,12 @@ befs_bt_get_key(struct super_block *sb, struct befs_btree_node *node,
 	else
 		prev_key_end = fs16_to_cpu(sb, keylen_index[index - 1]);
 
+	if (fs16_to_cpu(sb, keylen_index[index]) < prev_key_end ||
+	    fs16_to_cpu(sb, keylen_index[index]) > node->head.all_key_length) {
+		*keylen = 0;
+		return NULL;
+	}
+
 	*keylen = fs16_to_cpu(sb, keylen_index[index]) - prev_key_end;
 
 	return keystart + prev_key_end;
diff --git a/fs/befs/linuxvfs.c b/fs/befs/linuxvfs.c
index ee0cbae521b9..de3be4d61c80 100644
--- a/fs/befs/linuxvfs.c
+++ b/fs/befs/linuxvfs.c
@@ -222,7 +222,7 @@ befs_readdir(struct file *file, struct dir_context *ctx)
 	befs_off_t value;
 	int result;
 	size_t keysize;
-	char keybuf[BEFS_NAME_LEN + 1];
+	char keybuf[BEFS_NAME_LEN + 1] = { 0 };
 
 	befs_debug(sb, "---> %s name %pD, inode %llu, ctx->pos %lld",
 		  __func__, file, inode->i_ino, ctx->pos);