31

I am very often dealing with formatting USB drives, that are registered as /dev/sdX. This includes executing mkfs and fdisk and mount and other commands usually executed as root. However, I fear that accidentally I may mistype one single letter, and format my hard drive.

Ideally, I would want to set /dev/sdX* devices in "read-only" mode, so that any fdisk mkfs wipefs will fail on them, unless the device is switched to "read-write" mode by a manual command.

I thought chmod ugo-w /dev/sdX would work exactly like that. However, to my surprise, chmod 0000 /dev/sdc1 followed by mkfs /dev/sdc1 works completely fine.

How can I prevent all users, also root, from modifying a hard drive and its partition table and partitioning the hard-drive and writing to partitions in a way other than via a mounted file system? How can I enable writing to the drive with this method if I would want to?

I know I can make the device being owned by user. This, however, requires me to switch between user and root to make commands like chroot or mount or umount and this is a security hole. I do not want all USB storage devices being owned by user. I am searching for a better solution. Ideally, I would want to stay as the root user, just without the possibility of formatting the wrong disc.

5
  • 2
    Perhaps you could create a Linux control group (cgroup) which doesn't have access to the wrong device files. I don't have a reference to point you to, so I won't be writing a full answer. Others are welcome to.
    – Simppa
    Mar 12 at 18:22
  • Remember, a mounted partition can't be formatted. The root FS is always mounted while running.
    – Criggie
    Mar 13 at 4:08
  • 8
    Remember, a mounted partition can't be formatted Sure it can, try busybox mkfs.vfat. This is a quality of implementation issue. This is just a verbose check inside specific mkfs implementations, like github.com/util-linux/util-linux/blob/master/disk-utils/… . If specific mkfs is missing that check, you can. In busybox: github.com/brgl/busybox/blob/master/util-linux/mkfs_vfat.c#L298 . I tested, busybox formats mounted filesystem without any issues.
    – KamilCuk
    Mar 13 at 8:27
  • 4
    @KamilCuk In the end, the answer is that you should not be dealing with device nodes without double and triple checking what you're doing. The suggested answers all have gotchas when they fail and when you're doing admin work while sleepy the false security can bite you. Do not alias rm to rm -i, but make it a habit to use rm -i. Before formatting a device, use cfdisk to have a look at its partition table and size and use file to check the filesystem (or encryption header) on the device before formatting. Check what's on the device, before executing dangerous operations on the device
    – allo
    Mar 13 at 13:38
  • I suppose "use the minimum privilege necessary for your task" is not what you're looking for? That's not absolute protection, but it helps. Mar 14 at 21:04

11 Answers 11

38

Consider using /dev/disk/by-{path,id}/

/dev/disk/by-path/ has symlinks to block devices, where the names of the symlinks describe the "hardware path" of the device (subsystem, bus, controller, port, etc). A redacted listing on my system shows an nvme device, a sata device, and a USB drive (and the first partition on each):

/dev/disk/by-path
├── pci-0000:00:14.0-usb-0:1:1.0-scsi-0:0:0:0 -> ../../sdc
├── pci-0000:00:14.0-usb-0:1:1.0-scsi-0:0:0:0-part1 -> ../../sdc1
├── pci-0000:00:17.0-ata-2 -> ../../sda
├── pci-0000:00:17.0-ata-2-part1 -> ../../sda1
├── pci-0000:02:00.0-nvme-1 -> ../../nvme0n1
└── pci-0000:02:00.0-nvme-1-part1 -> ../../nvme0n1p1

/dev/disk/by-id works similarly, but has the subsystem, the device brand/model and serial number instead. I sure seem to have a lot of Samsung storage:

/dev/disk/by-id
├── ata-Samsung_SSD_860_EVO_500GB_XXXXXXXXXXXXXXX -> ../../sda
├── ata-Samsung_SSD_860_EVO_500GB_XXXXXXXXXXXXXXX-part1 -> ../../sda1
├── nvme-Samsung_SSD_980_PRO_1TB_XXXXXXXXXXXXXXX -> ../../nvme0n1
├── nvme-Samsung_SSD_980_PRO_1TB_XXXXXXXXXXXXXXX-part1 -> ../../nvme0n1p1
├── usb-Samsung_Flash_Drive_FIT_XXXXXXXXXXXXXXXX-0:0 -> ../../sdc
└── usb-Samsung_Flash_Drive_FIT_XXXXXXXXXXXXXXXX-0:0-part1 -> ../../sdc1

Assuming your goal is to not confuse plugged in USB drives and your (sata/nvme) system drive, these names may be helpful. You can just these names for your commands, and that should work as expected:

fdisk /dev/disk/by-id/usb-Samsung_Flash_Drive_FIT_XXXXXXXXXXXXXXXX-0:0
mke2fs /dev/disk/by-id/usb-Samsung_Flash_Drive_FIT_XXXXXXXXXXXXXXXX-0:0-part1

And hopefully this catches your eye enough to abort:

mke2fs /dev/disk/by-id/nvme-Samsung_SSD_980_PRO_1TB_XXXXXXXXXXXXXXX-part1
3
  • 7
    This I advocate as a best practice, using it almost always. Mar 12 at 21:41
  • 2
    If the target device is a partition and the partition table is GPT, you can set good, unique partition names (partlabels) for them. Then you can just go through /dev/disk/by-partlabel/uniquename. Since even with the most complex /dev/disk/by-id/usb-Samsung_Flash_Drive_FIT_XXXXXXXXXXXXXXXX-0:0-partN you could still somehow fumble the partition number when you type... Mar 13 at 10:05
  • 2
    @frostschutz That's also very useful! However OP was talking of formatting USB drives, so they may not have control over existing partition labels on those drives. That's why I focused on methods that include the subsystem (USB) in their name.
    – marcelm
    Mar 13 at 11:09
18

A simple ruse is to rm /dev/sda /dev/sda1 and so on after booting, if you want to prevent accidental reference to these devices. You can always re-create the inodes with mknod /dev/sda1 b 8 1 and so on if needed, but umount /dev/sda1 works without this, as it presumably has the appropriate information somewhere.

But it is really a question of good hygiene; preferably don't login as root, nor su to root, or if you must do so in only one terminal with a red background colour, don't copy-paste into it, write scripts for repetitive tasks and make sure they check the parameters and do other sanity checks.

6
  • This is the answer. rm device nodes you don't want to be accessible. In theory you could even lock them down fully by putting a seccomp filter on the whole system whereby the mknod syscall in its forms that create the device nodes is forbidden, Mar 12 at 18:14
  • or just rename them
    – ilkkachu
    Mar 13 at 18:51
  • 1
    You need to be root to format USB drives or to change permissions so they can be formatted by someone who isn't root, so your last paragraph is entirely unhelpful.
    – user253751
    Mar 13 at 19:08
  • 1
    @user253751 Not really. Writing a script for the repetitive task of formatting an USB drive is a perfectly valid suggestion, because a script will never make a typo when picking the destination drive. (Yes, there can be bugs, but let's assume you test the script thoroughly first.) You can also set up udev to make the appropriate block devices accessible to a dedicated non-root user, so that one is a valid suggestion as well.
    – TooTea
    Mar 13 at 22:19
  • @TooTea you say that, then you try to uninstall Bumblebee.
    – user253751
    Mar 13 at 22:25
12

Well, there is blockdev --setro to make a block device read-only (--setrw for read-write).

# blockdev --setro /dev/sdx3
# mkfs.ext4 /dev/sdx3
mke2fs 1.47.0 (5-Feb-2023)
/dev/sdx3 contains a vfat file system
Proceed anyway? (y,N) y
/dev/sdx3: Operation not permitted while setting up superblock
# wipefs -a /dev/sdx3
wipefs: /dev/sdcx3: failed to erase vfat magic string at
  offset 0x00000052: Operation not permitted

So can you make a block device read only even for root? Yes. Well, kind of.

However, it's not very reliable. For example, if the kernel re-reads partition table for any reason. Even if the partitions did not actually change, after a re-read /dev/sdx3 is technically a new device, which never had this ro flag set.

So at minimum you would need some udev rules that manage and maintain your readonly flags at all times. (If there is a flag anywhere that makes block devices readonly by default, I'm not aware of it.)

Also anything that is already using the device in write mode, may continue to do so. So this does not affect mounted filesystems ... until those filesystems try to re-open the device for any reason (when changing mount flags on re-mount or otherwise).

And of course making a partition readonly does not prevent something to write to the main device at an offset - or vice versa. It's normal to have multiple block devices pointing to the exact same storage area. You'd have to make sure to set all of them readonly, not just one.

So it's possible, yes, just not very practical to do.

Setting this flag might cause errors with other programs and such errors could lead just to a different kind of data loss. Normally you want writes to succeed or lose whatever was supposed to be written.

So it's entirely possible for this approach to cause more problems than it solves. In the end you still want to format your USB stick, for that you have to make it writeable, and for that you have to be able to identify and use the correct device name and not mix it up with a wrong name.

You can check under /dev/disk/by-*/* if there's a more suitable name, for example /dev/disk/by-id/usb-Flash… is unlikely to refer to an internal drive.

11

Not a real solution, but you could hide the dangerous commandos behind wrapping shell scripts that check the command line arguments for dangerous devices.

There might be also ways to use security systems like SELinux, AppArmor, ... to restrict access also for root.

2
  • 2
    This is the most secured answer, but not what most luddites want. Mar 12 at 23:20
  • 7
    That's a bit like aliasing ls to be ls -i - it might encourage recklessness that then bites you when using systems without those wrappers. Mar 13 at 9:21
6

You're looking for a technical solution to prevent you from doing something bad on your computer.

One way many of us learned to avoid these was by doing it wrong and then losing data. We learn to slow down and double-check things.

If you're able to learn from someone else's mistake, then that's great. I learn really well from my own mistakes.

A technical solution may work fine on your computer as it is now. Improving your approach to risky actions will help you on any computer in the future.

Unix is about small tools that do a small job well. Like sharp knifes and scissors and chisels, it’s all how you-the-user approach them.

Be comfortable with a tool, but never complacent.

5
  • 6
    Well, sadly, I do not think this is an answer to my question ;) I am a Linux administrator for about 20 years. No worries, I executed rm -rf / 3 times already, formatted wrong hard drives endless times, lost and misconfigured raids. You're looking for a technical solution to prevent you doing something bad on your computer. Yes exactly.
    – KamilCuk
    Mar 13 at 8:21
  • 4
    @KamilCuk yep I totally get where you're coming from there. The problem is when this safety net is gone, because you're using a different computer. It's like habitually shaking a car's gearstick because once you started it in-gear and rolled forward into a garage wall. I did once, and now I sometimes find myself doing the same safety check even on an automatic. But have I ever started in-gear again? No, never. And the day I don't shake the gearstick will be the day it was left in-gear. Or do you go replace all your scissors with safety scissors to avoid getting cut ?
    – Criggie
    Mar 13 at 9:12
  • 2
    Downvotes are ok - if readers think this answer is bad, go for it.
    – Criggie
    Mar 13 at 9:13
  • 2
    This is the best answer. "How do I avoid to mistype one letter, and mistakenly format the wrong drive?" "Always double- or triple-check what you're doing."
    – dr_
    Mar 13 at 9:25
  • 3
    What's wrong with that? You can argue that UIs that make everyday, normal and routine jobs and super-dangerous "destroy everything" commands easy to accidentally confuse have bad design. Formatting a sd-card or thumbdrive is a routine operation. Wiping your boot-partition is not. It's perfectly reasonable to want the latter to not easily happen when you intend the former.
    – Agrajag
    Mar 13 at 11:04
4

to my surprise, chmod 0000 /dev/sdc1 followed by mkfs /dev/sdc1 works completely fine.

As root, yes, since root is exempt from the usual file permission bit checks...

Now, I would have suggested not running as root but fixing the device permissions (through udev, likely) so that you can run mkfs under your usual user account. But you have a point on needing e.g. mount too.

So, you need to be able to be root for mount, but not for mkfs.

One way would be to arrange to run a session where you have some but not all of the capabilities that the superuser special rules actually rely on (on Linux). You'd need e.g. CAP_SYS_ADMIN for (u)mount, but would not want to have CAP_DAC_OVERRIDE, which is the one allows bypassing permission checks. I'll leave the implementation "as an exercise", though. You'll want to see capabilities(7) and setcap(8), in the least.

Another possibility would be to run the commands needing admin rights through sudo, and set the rules in sudoers so that you can run mkfs /dev/sdc, but not mkfs /dev/sda. One way to do that would be to have sudo only allow you to run a wrapper script, which then does any necessary checks.

Like this one, which runs mkfs using all the arguments it was given, but not if any of them contains /dev/sda:

#!/bin/sh
for arg in "$@"; do
    case $arg in 
        /dev/sda*)
         echo "I don't think you should touch 'sda', exiting..." >&2;
         exit 1;;
    esac
done
mkfs "$@"

Of course, those are basically sidestepping the question, but e.g. SELinux should be able to actually restrict root, if you care to go through the trouble of setting the policies accordingly.

3

To the best of my knowledge, you cannot do something to a system that root cannot undo unless you break something. Such a state would be considered an error condition, not a configuration.  You also have to account for the fact that you can destroy a partition table without formatting in the traditional sense, with something such as

dd if=/dev/zero of=/dev/sda bs=512 count=1 conv=notrunc

There are devices, however, that you can set to read-only mode.  Examples of two:

Since that would be write protect at the hardware level, any attempts to overcome it, as any user with any rights, would still fail.  The ability to alter the drive's contents in any way at that phase would be conscious will, not mistakes.

If the intent is just to prevent mistakes, jofel's answer of scripts / aliases is a +1.  But, as pointed out, this can lead to very bad habits.

3

I like to enforce the best-practice of marcelm's answer by a bash trap. You can save this to a file and source it in your current session. If you like it, you can add it to your .bashrc. Since this works on the local shell, it also covers direct usages of sudo (though not sudo -i).

#!/bin/bash
reject_devsdx () {
    if [[ $BASH_COMMAND =~ "/dev/sd" ]]
    then
        echo "Stop being stupid. Use /dev/disk/… for safety."
        return 1
    fi
}
shopt -s extdebug
trap reject_devsdx DEBUG

Inspired by ulidtko's answer. This question is about similar mechanics.

You can even add some fancy hints courtesy of this answer:

find /dev/disk -type l -printf '%p -> ' -exec readlink -f {} ';' | grep "$(echo ${BASH_COMMAND} | grep -Po '/dev/sd\S+')$"
1
  • This is rather nice, because it helps cultivate a safer habit that can apply even on systems without this code Apr 18 at 13:44
0

Echo your commands before running them.

This won't prevent you doing anything, but it's a habit I developed for more or less the same reason as:

I fear that accidentally I may mistype one single letter, and format my hard drive.

When you're about to run a dangerous command, first, run

echo dangerous_command

You can then check it's correct, press up, delete the word echo, and run it for real, knowing that the command itself is unchanged.

I find it particularly helpful for one-off loops with variable substitutions like

for a in ... ; do echo dangerous_command "$a" ; done

to make sure it's not about to e.g. run on the completely wrong set of files, but it can work just as well for simple, literal commands too.

Caveat: naturally, this won't work as easily if you've got things like pipes, redirection, or multiple commands involved, though there's usually similar things that can be done there.

Some utilities have --dry-run arguments - for that matter, mkfs.ext4 for example has a -n argument - though this is less helpful, as commands don't have to implement such an argument, and might go ahead with dangerous actions even if an unknown argument is present.

3
  • 3
    This won't work in may cases. Just think about the most simple way to write an iso to a device: cat file.iso >/dev/sdX. You won't write the file to the wrong device when you add an echo before the command, but you will overwrite the first byes of the device with cat file.iso.
    – allo
    Mar 15 at 13:51
  • I'm very aware of that, that's why I included the caveat. My answer is a suggestion, not a silver bullet. Probably worth mentioning: my habit is to use dd, not cat and redirection, for your task. Prefixing echo works there, and also works for OP's example of mkfs.
    – Keiji
    Mar 16 at 13:16
  • 1
    @Keiji in most use cases dd is the wrong tool Apr 18 at 13:42
0

Too short for an answer, really, but comments don't show code very well.

Don't you get a warning? Like this:

# mkfs -t ext4 /dev/sdg
mke2fs 1.46.2 (28-Feb-2021)
/dev/sdg contains a iso9660 file system labelled 'Debian 11.2.0 amd64 n'
Proceed anyway? (y,N) N

I often format different media, and it happens that I choose the wrong one - but the warning takes most, if not all, of the risk away.

1
  • This is a quality of implementation issue. busybox mkfs.vfat does not show a warning. dd if=/dev/zero of=/dev/sda does not show a warning. And this is only a warning. Very often I do want to repartition and reformat a disc.
    – KamilCuk
    Mar 15 at 16:00
0

You can remove and move the disc files that you want to protect. Add the following script to rc.local or other startup location:

mkdir -vp /dev/DO_NOT_TOUCH
mv -v /dev/sda* /dev/DO_NOT_TOUCH

That way you can still access them potentially with /dev/DO_NOT_TOUCH but it will be inaccessible under /dev/sda*.

I decided to also move all links from under /dev/disk with:

filter=(/dev/nvme*)
mkdir -vp /dev/DO_NOT_TOUCH
{
   printf "%s\n" "${filter[@]}"
   find /dev/disk -type l -exec readlink -nf {} ';' -exec printf "\t{}\n" ';' |
   grep -f <(printf "^%s\t\n" "${filter[@]}") |
   cut -f2
} |
rsync -axui --progress --remove-source-files --files-from - / /dev/DO_NOT_TOUCH

You must log in to answer this question.

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