diff --git a/block/Kconfig b/block/Kconfig index 03b53a50939fd0bfbb743b9465b5ea6385d04268..214c1caeab4c509e82db69cdbcb001f23a4dde16 100644 --- a/block/Kconfig +++ b/block/Kconfig @@ -239,6 +239,23 @@ config BLK_INLINE_ENCRYPTION_FALLBACK by falling back to the kernel crypto API when inline encryption hardware is not present. +config BLK_DEV_DETECT_WRITING_PART0 + bool "Detect writing to part0 when partitions mounted" + default n + help + When partitions of a block device are mounted, writing to part0's + buffer cache is likely going to cause filesystem corruption on + each partition. + As a supplement to BLK_DEV_WRITE_MOUNTED(_QUIET), enabling this + to detect the scenario above. + +config BLK_DEV_WRITE_MOUNTED_DUMP + bool "Dump info when detecting conflict of opening block device" + default n + help + Enabling this to dump info when opening mounted block devices + for write or trying to mount write opened block devices. + source "block/partitions/Kconfig" config BLK_MQ_PCI diff --git a/block/bdev.c b/block/bdev.c index dd39c26c44ad153484d879be859baede0dc5ab77..56ad4b9b66b45742fc2ec23f030813d0ddfecded 100644 --- a/block/bdev.c +++ b/block/bdev.c @@ -30,8 +30,20 @@ #include "../fs/internal.h" #include "blk.h" +#define OPEN_EXCLUSIVE "VFS: Open an exclusive opened block device for write" +#define OPEN_FOR_EXCLUSIVE "VFS: Open a write opened block device exclusively" + /* Should we allow writing to mounted block devices? */ -static bool bdev_allow_write_mounted = IS_ENABLED(CONFIG_BLK_DEV_WRITE_MOUNTED); +#define BLKDEV_ALLOW_WRITE_MOUNTED 0 +/* Should we detect writing to part0 when partitions mounted */ +#define BLKDEV_DETECT_WRITING_PART0 1 +/* Should we dump info when opening mounted block devices for write? */ +#define BLKDEV_WRITE_MOUNTED_DUMP 2 + +static u8 bdev_allow_write_mounted = + IS_ENABLED(CONFIG_BLK_DEV_WRITE_MOUNTED) << BLKDEV_ALLOW_WRITE_MOUNTED || + IS_ENABLED(CONFIG_BLK_DEV_DETECT_WRITING_PART0) << BLKDEV_DETECT_WRITING_PART0 || + IS_ENABLED(CONFIG_BLK_DEV_WRITE_MOUNTED_DUMP) << BLKDEV_WRITE_MOUNTED_DUMP; struct bdev_inode { struct block_device bdev; @@ -733,38 +745,94 @@ void blkdev_put_no_open(struct block_device *bdev) put_device(&bdev->bd_device); } +static void blkdev_dump_conflict_opener(struct block_device *bdev, char *msg) +{ + char name[BDEVNAME_SIZE]; + struct task_struct *p = NULL; + char comm_buf[TASK_COMM_LEN]; + pid_t p_pid; + + rcu_read_lock(); + p = rcu_dereference(current->real_parent); + task_lock(p); + strncpy(comm_buf, p->comm, TASK_COMM_LEN); + p_pid = p->pid; + task_unlock(p); + rcu_read_unlock(); + + snprintf(name, sizeof(name), "%pg", bdev); + pr_info_ratelimited("%s [%s]. current [%d %s]. parent [%d %s]\n", + msg, name, + current->pid, current->comm, p_pid, comm_buf); +} + static bool bdev_writes_blocked(struct block_device *bdev) { - return bdev->bd_writers == -1; + return !!bdev->bd_mounters; +} + +static bool bdev_lower_device_writes_blocked(struct block_device *bdev) +{ + if (bdev_allow_write_mounted & (1 << BLKDEV_DETECT_WRITING_PART0)) + return !!bdev->bd_holder; + else + return !!bdev->bd_holder && bdev->bd_holder != bd_may_claim; } static void bdev_block_writes(struct block_device *bdev) { - bdev->bd_writers = -1; + bdev->bd_mounters++; + if (bdev_is_partition(bdev) && + bdev_allow_write_mounted & (1 << BLKDEV_DETECT_WRITING_PART0)) + bdev_whole(bdev)->bd_mounters++; } static void bdev_unblock_writes(struct block_device *bdev) { - bdev->bd_writers = 0; + bdev->bd_mounters--; + if (bdev_is_partition(bdev) && + bdev_allow_write_mounted & (1 << BLKDEV_DETECT_WRITING_PART0)) + bdev_whole(bdev)->bd_mounters--; +} + +static bool bdev_mount_blocked(struct block_device *bdev) +{ + if (bdev_allow_write_mounted & (1 << BLKDEV_DETECT_WRITING_PART0)) + return bdev->bd_writers > 0 || bdev_whole(bdev)->bd_writers > 0; + return bdev->bd_writers > 0; +} + +bool bdev_may_conflict_open(struct block_device *bdev, char *msg) +{ + if (bdev_allow_write_mounted & (1 << BLKDEV_WRITE_MOUNTED_DUMP)) + blkdev_dump_conflict_opener(bdev, msg); + + if (bdev_allow_write_mounted & (1 << BLKDEV_ALLOW_WRITE_MOUNTED)) + return true; + + return false; } static bool bdev_may_open(struct block_device *bdev, blk_mode_t mode) { - if (bdev_allow_write_mounted) + if (bdev_allow_write_mounted & (1 << BLKDEV_ALLOW_WRITE_MOUNTED) && + !(bdev_allow_write_mounted & (1 << BLKDEV_WRITE_MOUNTED_DUMP))) return true; + /* Writes blocked? */ if (mode & BLK_OPEN_WRITE && bdev_writes_blocked(bdev)) - return false; - if (mode & BLK_OPEN_RESTRICT_WRITES && bdev->bd_writers > 0) - return false; + return bdev_may_conflict_open(bdev, OPEN_EXCLUSIVE); + if (mode & BLK_OPEN_RESTRICT_WRITES && bdev_mount_blocked(bdev)) + return bdev_may_conflict_open(bdev, OPEN_FOR_EXCLUSIVE); + if ((bdev_allow_write_mounted & (1 << BLKDEV_WRITE_MOUNTED_DUMP)) && + mode & BLK_OPEN_WRITE && bdev_lower_device_writes_blocked(bdev)) + blkdev_dump_conflict_opener(bdev, OPEN_EXCLUSIVE); + return true; } static void bdev_claim_write_access(struct block_device *bdev, blk_mode_t mode) { - if (bdev_allow_write_mounted) - return; - /* Claim exclusive or shared write access. */ if (mode & BLK_OPEN_RESTRICT_WRITES) bdev_block_writes(bdev); @@ -774,9 +842,6 @@ static void bdev_claim_write_access(struct block_device *bdev, blk_mode_t mode) static void bdev_yield_write_access(struct block_device *bdev, blk_mode_t mode) { - if (bdev_allow_write_mounted) - return; - /* Yield exclusive or shared write access. */ if (mode & BLK_OPEN_RESTRICT_WRITES) bdev_unblock_writes(bdev); @@ -1135,7 +1200,7 @@ void bdev_statx_dioalign(struct inode *inode, struct kstat *stat) static int __init setup_bdev_allow_write_mounted(char *str) { - if (kstrtobool(str, &bdev_allow_write_mounted)) + if (kstrtou8(str, 0, &bdev_allow_write_mounted)) pr_warn("Invalid option string for bdev_allow_write_mounted:" " '%s'\n", str); return 1; diff --git a/include/linux/blk_types.h b/include/linux/blk_types.h index 262ae789726b1f2a127aa4f31d147b3c68873bc4..0e1e429d3f74a05985517e54c8754679791142ad 100644 --- a/include/linux/blk_types.h +++ b/include/linux/blk_types.h @@ -70,6 +70,7 @@ struct block_device { bool bd_make_it_fail; #endif int bd_writers; + int bd_mounters; /* * keep this out-of-line as it's both big and not needed in the fast * path