From: Cen Zhang <zzzccc427@gmail.com>
To: mark@fasheh.com, jlbec@evilplan.org, joseph.qi@linux.alibaba.com
Cc: heming.zhao@suse.com, ocfs2-devel@lists.linux.dev,
linux-kernel@vger.kernel.org, stable@vger.kernel.org,
baijiaju1990@gmail.com, jjzuming@gmail.com, zzzccc427@gmail.com
Subject: [PATCH v2] ocfs2/cluster: freeze heartbeat configuration during device setup
Date: Tue, 20 Oct 2026 22:53:50 +0800 [thread overview]
Message-ID: <pm-ocfs2-objects-candidate-0077-v7-34980443ec254b2a1ffc@gmail.com> (raw)
A heartbeat region's geometry must remain stable from device setup until
its thread stops and its buffers are unmapped. The configuration stores
check hr_bdev_file before changing blocks, block_bytes or start_block,
but separate configfs opens can run these callbacks concurrently with
device setup. The buffer mutex is per open and frag_sem is held for read.
A delayed blocks store can enlarge hr_blocks after the slot array has
been allocated. A block_bytes store can change the block size and shift
after hr_tmp_block and the slot pages have been sized, so the heartbeat
thread copies or clears beyond its buffers. A start_block store can
change the I/O location after it has been validated, including to zero.
Device setup also writes hr_node_num before acquiring the device guard.
A second dev writer can overwrite a starting region's node number and
return -EINVAL if the local node has been disabled. The heartbeat thread
then uses a different node number when releasing its node dependency.
Serialize all three configuration updates with device publication under
o2hb_live_lock. Validate the required geometry in that same critical
section, so a start_block update to zero during device open is rejected.
Keep the node number local until the writer wins the publication check,
then publish it with the device file. A rejected writer leaves the
region's node number unchanged.
Keep device open and failed-start cleanup outside the spinlock. Clear
the device under the same lock only after the thread has stopped and
the buffers have been unmapped, allowing configuration writes to resume.
KASAN report as below:
BUG: KASAN: slab-out-of-bounds in o2hb_thread+0xc62/0xe90
Read of size 1 at addr ffff888107829ac0 by task o2hb-pmbd-regio/497
CPU: 3 UID: 0 PID: 497 Comm: o2hb-pmbd-regio Not tainted 7.3.0-rc4-next-20260921-pmb-ocfs2-functional-v1+ #1 PREEMPT(lazy)
Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
<TASK>
dump_stack_lvl+0x93/0xd0
print_report+0xce/0x630
? o2hb_thread+0xc62/0xe90
? srso_alias_return_thunk+0x5/0xfbef5
? __virt_addr_valid+0x20e/0x420
? o2hb_thread+0xc62/0xe90
kasan_report+0xe0/0x110
? o2hb_thread+0xc62/0xe90
o2hb_thread+0xc62/0xe90
? srso_alias_return_thunk+0x5/0xfbef5
? __pfx_o2hb_thread+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
? srso_alias_return_thunk+0x5/0xfbef5
? srso_alias_return_thunk+0x5/0xfbef5
? __lock_acquire+0x5a3/0x2140
? srso_alias_return_thunk+0x5/0xfbef5
? lockdep_hardirqs_on_prepare+0xeb/0x1a0
? srso_alias_return_thunk+0x5/0xfbef5
? trace_hardirqs_on+0x18/0x160
? srso_alias_return_thunk+0x5/0xfbef5
? srso_alias_return_thunk+0x5/0xfbef5
? __pfx_o2hb_thread+0x10/0x10
? kthread+0x351/0x460
kthread+0x351/0x460
? srso_alias_return_thunk+0x5/0xfbef5
? __pfx_kthread+0x10/0x10
ret_from_fork+0x659/0x940
? __pfx_ret_from_fork+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
? __switch_to+0x74f/0xf70
? __pfx_kthread+0x10/0x10
ret_from_fork_asm+0x1a/0x30
</TASK>
Allocated by task 496:
kasan_save_stack+0x33/0x60
kasan_save_track+0x14/0x30
__kasan_kmalloc+0xaa/0xb0
__kmalloc_noprof+0x2de/0x780
o2hb_region_dev_store+0x7c4/0x1c70
configfs_write_iter+0x2f9/0x4f0
vfs_write+0x639/0x1010
ksys_write+0x111/0x200
do_syscall_64+0x114/0x620
entry_SYSCALL_64_after_hwframe+0x77/0x7f
The buggy address belongs to the object at ffff888107829a80
which belongs to the cache kmalloc-64 of size 64
The buggy address is located 8 bytes to the right of
allocated 56-byte region [ffff888107829a80, ffff888107829ab8)
(Page and memory-state dump omitted.)
Fixes: a7f6a5fb4bde ("[PATCH] OCFS2: The Second Oracle Cluster Filesystem")
Cc: stable@vger.kernel.org
Signed-off-by: Cen Zhang <zzzccc427@gmail.com>
Assisted-by: LLM
---
Changes in v2:
- Serialize block_bytes and start_block updates with device publication
under o2hb_live_lock, and update block_bytes/block_bits together.
- Validate the required geometry in the publication critical section,
including a start_block change to zero during device open.
- Keep node_num local until the writer wins the device guard, so rejected
dev writes cannot change the active region; retain the blocks protection
and failed-start cleanup ordering from v1.
Link to v1: https://lore.kernel.org/r/pm-ocfs2-objects-candidate-0077-v6-eb25e73523a3cb94fae0@gmail.com
fs/ocfs2/cluster/heartbeat.c | 65 +++++++++++++++++++++++-------------
1 file changed, 41 insertions(+), 24 deletions(-)
diff --git a/fs/ocfs2/cluster/heartbeat.c b/fs/ocfs2/cluster/heartbeat.c
index b45adc39aacc..63a538b66feb 100644
--- a/fs/ocfs2/cluster/heartbeat.c
+++ b/fs/ocfs2/cluster/heartbeat.c
@@ -1681,16 +1681,19 @@ static ssize_t o2hb_region_block_bytes_store(struct config_item *item,
unsigned long block_bytes;
unsigned int block_bits;
- if (reg->hr_bdev_file)
- return -EINVAL;
-
status = o2hb_read_block_input(reg, page, &block_bytes,
&block_bits);
if (status)
return status;
+ spin_lock(&o2hb_live_lock);
+ if (reg->hr_bdev_file) {
+ spin_unlock(&o2hb_live_lock);
+ return -EINVAL;
+ }
reg->hr_block_bytes = (unsigned int)block_bytes;
reg->hr_block_bits = block_bits;
+ spin_unlock(&o2hb_live_lock);
return count;
}
@@ -1710,14 +1713,17 @@ static ssize_t o2hb_region_start_block_store(struct config_item *item,
char *p = (char *)page;
ssize_t ret;
- if (reg->hr_bdev_file)
- return -EINVAL;
-
ret = kstrtoull(p, 0, &tmp);
if (ret)
return -EINVAL;
+ spin_lock(&o2hb_live_lock);
+ if (reg->hr_bdev_file) {
+ spin_unlock(&o2hb_live_lock);
+ return -EINVAL;
+ }
reg->hr_start_block = tmp;
+ spin_unlock(&o2hb_live_lock);
return count;
}
@@ -1736,9 +1742,6 @@ static ssize_t o2hb_region_blocks_store(struct config_item *item,
char *p = (char *)page;
int ret;
- if (reg->hr_bdev_file)
- return -EINVAL;
-
ret = kstrtoul(p, 0, &tmp);
if (ret)
return ret;
@@ -1746,7 +1749,13 @@ static ssize_t o2hb_region_blocks_store(struct config_item *item,
if (tmp > O2NM_MAX_NODES || tmp == 0)
return -ERANGE;
+ spin_lock(&o2hb_live_lock);
+ if (reg->hr_bdev_file) {
+ spin_unlock(&o2hb_live_lock);
+ return -EINVAL;
+ }
reg->hr_blocks = (unsigned int)tmp;
+ spin_unlock(&o2hb_live_lock);
return count;
}
@@ -1878,19 +1887,18 @@ static ssize_t o2hb_region_dev_store(struct config_item *item,
{
struct o2hb_region *reg = to_o2hb_region(item);
struct task_struct *hb_task;
+ struct file *bdev_file;
long fd;
int sectsize;
char *p = (char *)page;
ssize_t ret = -EINVAL;
int live_threshold;
-
- if (reg->hr_bdev_file)
- return -EINVAL;
+ u8 node_num;
/* We can't heartbeat without having had our node number
* configured yet. */
- reg->hr_node_num = o2nm_this_node();
- if (reg->hr_node_num == O2NM_MAX_NODES)
+ node_num = o2nm_this_node();
+ if (node_num == O2NM_MAX_NODES)
return -EINVAL;
ret = kstrtol(p, 0, &fd);
@@ -1904,20 +1912,27 @@ static ssize_t o2hb_region_dev_store(struct config_item *item,
if (fd_empty(f))
return -EINVAL;
- if (reg->hr_blocks == 0 || reg->hr_start_block == 0 ||
- reg->hr_block_bytes == 0)
- return -EINVAL;
-
if (!S_ISBLK(fd_file(f)->f_mapping->host->i_mode))
return -EINVAL;
- reg->hr_bdev_file = bdev_file_open_by_dev(fd_file(f)->f_mapping->host->i_rdev,
- BLK_OPEN_WRITE | BLK_OPEN_READ, NULL, NULL);
- if (IS_ERR(reg->hr_bdev_file)) {
- ret = PTR_ERR(reg->hr_bdev_file);
- reg->hr_bdev_file = NULL;
- return ret;
+ bdev_file =
+ bdev_file_open_by_dev(fd_file(f)->f_mapping->host->i_rdev,
+ BLK_OPEN_WRITE | BLK_OPEN_READ,
+ NULL, NULL);
+ if (IS_ERR(bdev_file))
+ return PTR_ERR(bdev_file);
+
+ /* Freeze the geometry and node number before starting setup. */
+ spin_lock(&o2hb_live_lock);
+ if (reg->hr_bdev_file || reg->hr_blocks == 0 ||
+ reg->hr_start_block == 0 || reg->hr_block_bytes == 0) {
+ spin_unlock(&o2hb_live_lock);
+ fput(bdev_file);
+ return -EINVAL;
}
+ reg->hr_node_num = node_num;
+ reg->hr_bdev_file = bdev_file;
+ spin_unlock(&o2hb_live_lock);
sectsize = bdev_logical_block_size(reg_bdev(reg));
if (sectsize != reg->hr_block_bytes) {
@@ -2036,7 +2051,9 @@ out:
o2hb_unmap_slot_data(reg);
fput(reg->hr_bdev_file);
+ spin_lock(&o2hb_live_lock);
reg->hr_bdev_file = NULL;
+ spin_unlock(&o2hb_live_lock);
}
return ret;
}
reply other threads:[~2026-10-20 14:54 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=pm-ocfs2-objects-candidate-0077-v7-34980443ec254b2a1ffc@gmail.com \
--to=zzzccc427@gmail.com \
--cc=baijiaju1990@gmail.com \
--cc=heming.zhao@suse.com \
--cc=jjzuming@gmail.com \
--cc=jlbec@evilplan.org \
--cc=joseph.qi@linux.alibaba.com \
--cc=linux-kernel@vger.kernel.org \
--cc=mark@fasheh.com \
--cc=ocfs2-devel@lists.linux.dev \
--cc=stable@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®