8

How to avoid any type of partition probes when a HDD is detected by a booted up system, in a way that it only detects basic drive features as model, sector size and capacity, consequently creating just one device file as /dev/sda? Is it a libata/kernel issue?

It's about a data recovery experience with a damaged HDD that sectors corresponding to partition table should not being read. I'm not talking about auto-mount, it's already disabled.

The sectors linked to partitions are on a scratched area. When heads pass over there, drive crashes.

This is what happens when a healthy HDD is attached:

Dmesg Output

Oct 13 16:21:42 wks-01 kernel: [ 906.796660] sd 8:0:0:0: [sdb] 1953525167 512-byte logical blocks: (1.00 TB/931 GiB)...
Oct 13 16:21:42 wks-01 kernel: [ 906.915646] sdb: sdb1

(I need to disable that last routine)

Udev Output

KERNEL[906.915935] add /devices/pci0000:00/0000:00:1d.7/usb8/8-3/8-3:1.0/host8/target8:0:0/8:0:0:0/block/sdb (block)<br>
KERNEL[906.915999] add /devices/pci0000:00/0000:00:1d.7/usb8/8-3/8-3:1.0/host8/target8:0:0/8:0:0:0/block/sdb/**sdb1** (block)  (**I need to disable this routine)** ... <br>
UDEV [907.392087] add /devices/pci0000:00/0000:00:1d.7/usb8/8-3/8-3:1.0/host8/target8:0:0/8:0:0:0/block/sdb/sdb1 (block)
8
  • 1
    Why don't you want to read the partition table? The kernel will not write anything to the disk until you issue some command from userspace.
    – grochmal
    Oct 12, 2016 at 21:10
  • Because those sectors shouldn't even be read. They're on a scratched area. When heads pass over there, drive crashes. Thanks for your reply.
    – William
    Oct 13, 2016 at 16:29
  • Hm... interesting, to not read the drive you would need to disable the right kernel modules. My take on it would be to rmmod sd_mod scsi_mod. But you can't do it if your system is booted from a scsi disk (e.g. a USB stick or CD drive, or an actual disk), which isn't really feasible (maybe a network boot?). A better approach may be to let the disk crash an then power it back with hdparm. What happens when it crashes?
    – grochmal
    Oct 13, 2016 at 16:56
  • I need to disable probing for partitions, not hdd detection. Thanks.
    – William
    Oct 13, 2016 at 20:00
  • I think you're on the right path with udev, i forgot udev. On the kernel side libata and ata_generic should deal with the disk and the scsi stuff with the partitions, but I'm not that confident in that. Nevertheless I've edited your question to look a little better and tagged with udev since we want to attract people that are good with udev config.
    – grochmal
    Oct 14, 2016 at 0:31

4 Answers 4

2

Update: I've posted a quicker/safer method below using a kernel module

As @grochmal notes, there's no built in way to do this, although if you are willing to compile your own kernel, it's pretty straight forward:

In the file block/partition-generic.c add the following code before the function rescan_partitions:

int do_partscan = 1;

static const struct kernel_param_ops do_partscan_param_ops = {
    .set    = param_set_int,
    .get    = param_get_int,
};

module_param_cb(do_partscan, &do_partscan_param_ops, &do_partscan, 0644);

and insert this code at the beginning of that function:

if (!do_partscan) {
    bdev->bd_invalidated = 0;
    return 0;
}

this will provide you with a module parameter (in /sys/module/... that you can use to toggle partition scanning. If it is set to 0 any scan will return immediately with no partitions. If you need to, you can flip it back to 1 and run blockdev --rereadpt <device> to load partitions on an attached disk.

[I'm not sure where the param will be in the /sys/module tree - look for it with find /sys/module -name do_partscan. I think you can set

#undef MODULE_PARAM_PREFIX
#define MODULE_PARAM_PREFIX "block."

before the code module_param_cb ... to put it into /sys/module/block/parameters/do_partscan but I haven't tested it like that.]

1

There isn't a way.

From the UDEV perspective the partition uevents are sent directly from the kernel, there is no indirection.

From the kernel side things happen from __blkdev_get() which will always read at least some of the partition table with disk_get_part(). That will read enough of the partition table to understand what kind of partition table it is.

As far as you can go is to unset CONFIG_MSDOS_PARTITION during kernel compilation so that msdos_partition() it will not be used inside check_partition(). I'm not sure how much of the partition it reads more compared to disk_get_part() though.

Notes

  • This is assuming that your disk uses an MSDOS partition. There are several other CONFIG_*_PARTIOTION parameters in /block/partitions/check.c.
  • You would need to boot this kernel from a drive that uses a different partition type than the one you want to backup. This may or may not be troublesome (GPT partitions are quite viable today).
  • Another way is that, maybe, you could rmmod scsi. But that would require you to not need the SCSI subsystem for anything. The only way I can think you can achieve that is through a network boot. You could then connect the damaged disk, use modprobe scsi to get the subsystem back, and make the node (mknod) by hand. This is hypothetical (I have not tried this), I'm not sure if mknod will not trigger __blkdev_get() screwing all the effort.
1
  • Do you have any comments on my new answers? Feb 17, 2022 at 23:55
1

The best solution I found works from kernel 2.6 and up; It works by using kretprobe to intercept the add_disk function and setting a flag that blocks partition reads. When the function returns, the flag is restored to its original state. This way, you can manually read the partitions later (using partprobe etc.).

This method also allows disabling\reenabling the block on the fly, by setting the state in /sys/module/no_partscan/parameters/enabled to 0 or 1.

I have tested it on AMD64 but not on x86 or ARM. The code relies on picking the registers that correspond to the function arguments; that is defined per-architecture and may be incorrect.

Here is the code for the module:

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/version.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kprobes.h>
#include <linux/genhd.h>

#ifdef CONFIG_X86_32
#define ARG1 ax
#define ARG2 bx
#elif defined CONFIG_X86_64
#define ARG1 di
#define ARG2 si
#elif defined CONFIG_ARM || CONFIG_ARM64
#define ARG1 regs[0]
#define ARG2 regs[1]
#endif

#if LINUX_VERSION_CODE <= KERNEL_VERSION(4,7,10)
#define ARG ARG1
static char func_name[NAME_MAX] = "add_disk";
#else // after 4.7.10, add_disk is a macro pointing to device_add_disk, which has the disk as its 2nd argument
#define ARG ARG2
static char func_name[NAME_MAX] = "device_add_disk";
#endif

static int enabled = 1;
module_param(enabled, int, 0664);
MODULE_PARM_DESC(enabled, "Enable intercepting disk initializing so we can block partscan.");

struct instance_data {
    struct gendisk *disk;
};

static int entry_handler(struct kretprobe_instance *ri, struct pt_regs *regs)
{
    struct instance_data *data;
    struct gendisk *disk;

    data = (struct instance_data *)ri->data;
    disk = (struct gendisk *)(regs->ARG);

    if (!enabled || disk->flags & GENHD_FL_NO_PART_SCAN) {
        data->disk = NULL;
    } else {
        pr_warn("Intercepted partition read for disk: %s.\n", disk->disk_name);
        disk->flags |= (GENHD_FL_NO_PART_SCAN);
        data->disk = disk; // store this so we can remove the NO_PARTSCAN flag on function return
    }
    return 0;
}

static int ret_handler(struct kretprobe_instance *ri, struct pt_regs *regs)
{
    struct instance_data *data;

    data = (struct instance_data *)ri->data;
    if (data->disk)
        data->disk->flags &= ~(GENHD_FL_NO_PART_SCAN);

    return 0;
}

static struct kretprobe my_kretprobe = {
    .handler        = ret_handler,
    .entry_handler  = entry_handler,
    .data_size      = sizeof(struct instance_data),
    .maxactive      = 20,
};

static int __init kretprobe_init(void)
{
    int ret;

    my_kretprobe.kp.symbol_name = func_name;
    ret = register_kretprobe(&my_kretprobe);
    if (ret < 0) {
        pr_warn("register_kretprobe failed, returned %d\n", ret);
        return ret;
    }
    return 0;
}

static void __exit kretprobe_exit(void)
{
    unregister_kretprobe(&my_kretprobe);
    pr_info("kretprobe at unregistered\n");

    /* nmissed > 0 suggests that maxactive was set too low. */
    if (my_kretprobe.nmissed) pr_warn("Missed probing %d instances.\n", my_kretprobe.nmissed);
}

module_init(kretprobe_init)
module_exit(kretprobe_exit)
MODULE_LICENSE("GPL");

It can be built and inserted just like the livepatch module in the other answer.

1
1

From Linux kernel 4 and on, you can use a livepatch module to block reading the partition table. This is much faster and easier than recompiling the entire kernel. I've created a module that covers the various forms of the livepatch api using IF macros.

This has been tested on 5.13.0-28 under Ubuntu Server 20.10. I have not tried on other kernels - the code may be incorrect.

Here is no_partscan.c:

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/version.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/livepatch.h>

#include <linux/fs.h>
#include <linux/genhd.h>

#if LINUX_VERSION_CODE <= KERNEL_VERSION(5,4,179)

static int livepatch_rescan_partitions(struct gendisk *disk, struct block_device *bdev)
{
    pr_warn("Intercepted partition read for disk: %s.\n", disk->disk_name);
    return -EIO;
}

static struct klp_func funcs[] = {
    {
        .old_name = "rescan_partitions",
        .new_func = livepatch_rescan_partitions,
    }, { }
};

#else

static int livepatch_blk_add_partitions(struct gendisk *disk)
{
    pr_warn("Intercepted partition read for disk: %s.\n", disk->disk_name);
    return 0;
}

#endif

static struct klp_func funcs[] = {
    {
        .old_name = "blk_add_partitions",
        .new_func = livepatch_blk_add_partitions,
    }, { }
};

static struct klp_object objs[] = {
    {
        /* name being NULL means vmlinux */
        .funcs = funcs,
    }, { }
};

static struct klp_patch patch = {
    .mod = THIS_MODULE,
    .objs = objs,
};

#if LINUX_VERSION_CODE <= KERNEL_VERSION(5,0,21)

static int livepatch_init(void)
{
    int ret;

#if LINUX_VERSION_CODE <= KERNEL_VERSION(4,15,18) && LINUX_VERSION_CODE >= KERNEL_VERSION(4,11,12)
    if (!klp_have_reliable_stack() && !patch.immediate) {
         // Use of this option will also prevent removal of the patch.
         // See Documentation/livepatch/livepatch.txt for more details.
        patch.immediate = true;
        pr_notice("The consistency model isn't supported for your architecture.  Bypassing safety mechanisms and applying the patch immediately.\n");
    }
#endif

    ret = klp_register_patch(&patch);
    if (ret)
        return ret;
    ret = klp_enable_patch(&patch);
    if (ret) {
        WARN_ON(klp_unregister_patch(&patch));
        return ret;
    }
    return 0;
}

static void livepatch_exit(void)
{
#if LINUX_VERSION_CODE <= KERNEL_VERSION(4,11,12)
    WARN_ON(klp_disable_patch(&patch)); 
#endif
    WARN_ON(klp_unregister_patch(&patch));
}

#else

static int livepatch_init(void)
{
    return klp_enable_patch(&patch);
}

static void livepatch_exit(void)
{
}

#endif

module_init(livepatch_init);
module_exit(livepatch_exit);
MODULE_LICENSE("GPL");
MODULE_INFO(livepatch, "Y");

It can be used with the following Makefile:

obj-m := no_partscan.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
    $(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
    $(MAKE) -C $(KDIR) M=$(PWD) clean

(Please note that if you see a "missing separator" running that Makefile, replace the four spaces with a tab.)

Afterwards, when you are ready to block the partition reads, use:

sudo insmod no_partscan

To disable the patch, use:

echo 0 > /sys/kernel/livepatch/no_partscan/enabled

This will remove the patch but it is still installed as a module; you will have to run sudo rmmod no_partscan.ko and then insmod etc. again to reenable it.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .