| From 69c4d51563874ec0c30292915a7fc80b992d26db Mon Sep 17 00:00:00 2001 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Thu, 3 Jul 2025 14:49:12 -0700 |
| Subject: hfs: fix slab-out-of-bounds in hfs_bnode_read() |
| |
| From: Viacheslav Dubeyko <slava@dubeyko.com> |
| |
| [ Upstream commit a431930c9bac518bf99d6b1da526a7f37ddee8d8 ] |
| |
| This patch introduces is_bnode_offset_valid() method that checks |
| the requested offset value. Also, it introduces |
| check_and_correct_requested_length() method that checks and |
| correct the requested length (if it is necessary). These methods |
| are used in hfs_bnode_read(), hfs_bnode_write(), hfs_bnode_clear(), |
| hfs_bnode_copy(), and hfs_bnode_move() with the goal to prevent |
| the access out of allocated memory and triggering the crash. |
| |
| Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com> |
| Link: https://lore.kernel.org/r/20250703214912.244138-1-slava@dubeyko.com |
| Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| fs/hfs/bnode.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++ |
| 1 file changed, 92 insertions(+) |
| |
| diff --git a/fs/hfs/bnode.c b/fs/hfs/bnode.c |
| index cb823a8a6ba9..1dac5d9c055f 100644 |
| --- a/fs/hfs/bnode.c |
| +++ b/fs/hfs/bnode.c |
| @@ -15,6 +15,48 @@ |
| |
| #include "btree.h" |
| |
| +static inline |
| +bool is_bnode_offset_valid(struct hfs_bnode *node, int off) |
| +{ |
| + bool is_valid = off < node->tree->node_size; |
| + |
| + if (!is_valid) { |
| + pr_err("requested invalid offset: " |
| + "NODE: id %u, type %#x, height %u, " |
| + "node_size %u, offset %d\n", |
| + node->this, node->type, node->height, |
| + node->tree->node_size, off); |
| + } |
| + |
| + return is_valid; |
| +} |
| + |
| +static inline |
| +int check_and_correct_requested_length(struct hfs_bnode *node, int off, int len) |
| +{ |
| + unsigned int node_size; |
| + |
| + if (!is_bnode_offset_valid(node, off)) |
| + return 0; |
| + |
| + node_size = node->tree->node_size; |
| + |
| + if ((off + len) > node_size) { |
| + int new_len = (int)node_size - off; |
| + |
| + pr_err("requested length has been corrected: " |
| + "NODE: id %u, type %#x, height %u, " |
| + "node_size %u, offset %d, " |
| + "requested_len %d, corrected_len %d\n", |
| + node->this, node->type, node->height, |
| + node->tree->node_size, off, len, new_len); |
| + |
| + return new_len; |
| + } |
| + |
| + return len; |
| +} |
| + |
| void hfs_bnode_read(struct hfs_bnode *node, void *buf, int off, int len) |
| { |
| struct page *page; |
| @@ -22,6 +64,20 @@ void hfs_bnode_read(struct hfs_bnode *node, void *buf, int off, int len) |
| int bytes_read; |
| int bytes_to_read; |
| |
| + if (!is_bnode_offset_valid(node, off)) |
| + return; |
| + |
| + if (len == 0) { |
| + pr_err("requested zero length: " |
| + "NODE: id %u, type %#x, height %u, " |
| + "node_size %u, offset %d, len %d\n", |
| + node->this, node->type, node->height, |
| + node->tree->node_size, off, len); |
| + return; |
| + } |
| + |
| + len = check_and_correct_requested_length(node, off, len); |
| + |
| off += node->page_offset; |
| pagenum = off >> PAGE_SHIFT; |
| off &= ~PAGE_MASK; /* compute page offset for the first page */ |
| @@ -80,6 +136,20 @@ void hfs_bnode_write(struct hfs_bnode *node, void *buf, int off, int len) |
| { |
| struct page *page; |
| |
| + if (!is_bnode_offset_valid(node, off)) |
| + return; |
| + |
| + if (len == 0) { |
| + pr_err("requested zero length: " |
| + "NODE: id %u, type %#x, height %u, " |
| + "node_size %u, offset %d, len %d\n", |
| + node->this, node->type, node->height, |
| + node->tree->node_size, off, len); |
| + return; |
| + } |
| + |
| + len = check_and_correct_requested_length(node, off, len); |
| + |
| off += node->page_offset; |
| page = node->page[0]; |
| |
| @@ -104,6 +174,20 @@ void hfs_bnode_clear(struct hfs_bnode *node, int off, int len) |
| { |
| struct page *page; |
| |
| + if (!is_bnode_offset_valid(node, off)) |
| + return; |
| + |
| + if (len == 0) { |
| + pr_err("requested zero length: " |
| + "NODE: id %u, type %#x, height %u, " |
| + "node_size %u, offset %d, len %d\n", |
| + node->this, node->type, node->height, |
| + node->tree->node_size, off, len); |
| + return; |
| + } |
| + |
| + len = check_and_correct_requested_length(node, off, len); |
| + |
| off += node->page_offset; |
| page = node->page[0]; |
| |
| @@ -119,6 +203,10 @@ void hfs_bnode_copy(struct hfs_bnode *dst_node, int dst, |
| hfs_dbg(BNODE_MOD, "copybytes: %u,%u,%u\n", dst, src, len); |
| if (!len) |
| return; |
| + |
| + len = check_and_correct_requested_length(src_node, src, len); |
| + len = check_and_correct_requested_length(dst_node, dst, len); |
| + |
| src += src_node->page_offset; |
| dst += dst_node->page_offset; |
| src_page = src_node->page[0]; |
| @@ -136,6 +224,10 @@ void hfs_bnode_move(struct hfs_bnode *node, int dst, int src, int len) |
| hfs_dbg(BNODE_MOD, "movebytes: %u,%u,%u\n", dst, src, len); |
| if (!len) |
| return; |
| + |
| + len = check_and_correct_requested_length(node, src, len); |
| + len = check_and_correct_requested_length(node, dst, len); |
| + |
| src += node->page_offset; |
| dst += node->page_offset; |
| page = node->page[0]; |
| -- |
| 2.39.5 |
| |