Subject: bug with securelevel >= 1 vs. read-only mounted "disks"
To: NetBSD-current Discussion List <current-users@NetBSD.ORG>
From: Greg A. Woods <woods@weird.com>
List: current-users
Date: 03/16/2001 00:03:44
Today I spent most of the day recovering my router's root filesystem
after accidentally writing a floppy image over the beginning of it.  I
had needed to make a floppy this morning and tried making it on my
router, but I was confused by a number of syslog messages spewing on the
screen as I was typing and managed to hit return before I reviewed my
command line....  :-(

(And the worst part was I'd just finished rebuilding it almost from
scratch earlier this week after the motherboard had failed during an
attempt to upgrade it!)

At first I blamed myself for not setting the securelevel up high enough
to prevent such stupid mistakes.  Now that I've got the machine up and
running (mostly) fine again I've discovered that I did indeed have the
securelevel set to one!

If I understand this chunk of code from miscfs/specfs/spec_vnops.c
correctly, only opens of the actual block device node corresponding to
the character device currently being opened will be blocked, but other
potentially overlapping partitions are still left writable (or even
readable in the case of securelevel>=2).

        case VCHR:
                if ((u_int)maj >= nchrdev)
                        return (ENXIO);
                if (ap->a_cred != FSCRED && (ap->a_mode & FWRITE)) {
                        /*
                         * When running in very secure mode, do not allow
                         * opens for writing of any disk character devices.
                         */
                        if (securelevel >= 2 && cdevsw[maj].d_type == D_DISK)
                                return (EPERM);
                        /*
                         * When running in secure mode, do not allow opens
                         * for writing of /dev/mem, /dev/kmem, or character
                         * devices whose corresponding block devices are
                         * currently mounted.
                         */
                        if (securelevel >= 1) {
                                if ((bdev = chrtoblk(dev)) != (dev_t)NODEV &&
                                    vfinddev(bdev, VBLK, &bvp) &&
                                    bvp->v_usecount > 0 &&
                                    (error = vfs_mountedon(bvp)))
                                        return (error);
                                if (iskmemdev(dev))
                                        return (EPERM);
                        }
                }
                if (cdevsw[maj].d_type == D_TTY)
                        vp->v_flag |= VISTTY;
                VOP_UNLOCK(vp, 0);
                error = (*cdevsw[maj].d_open)(dev, ap->a_mode, S_IFCHR, p);
                vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
                return (error);

The manual [init(8)] however uses the word "disk", not "devices" in its
discussion of securelevel values:

     1     Secure mode - system immutable and system append-only flags may not
           be turned off; disks for mounted filesystems, /dev/mem, and
           /dev/kmem are read-only.

Seems the manual is wrong, or at least very misleading....

The word "disk" to me implies the entire physical device, not just the
corresponding partition, so either all device nodes corresponding to the
same physical spindle of any mounted filesystem must be blocked from
being opened, or at minimum all nodes of any partitions that possibly
overlap mounted partitions, should be blocked.  The latter is of course
a more complex check, but both checks would seem to require assistance
from the device driver in some way or another since that's where the
determination of which minor's match any given spindle is made.

Unless I'm mistaken this is a very serious and wide open security hole
(even if it only protects against fat fingers), since every disk has a
fully overlapping partition by default (and i386 systems have two that
would meet most hacker's needs).  The current situation is an almost
totally false sense of security!  (It might be possible to block such
opens, and any attempts to update the partition table, without going to
a securelevel > 1 by removing all overlapping partitions, but that's as
equally drastic and un-desirable as upping the securelevel!)

I don't know if this particular aspect of this issue has been discussed
before or not, but I certainly want to raise it again now after having
been bit by it!

I'd very much prefer fixing the code rather than documenting the current
behaviour.  The securelevel feature is indeed a lot too coarse-grained,
but it's helpful none-the-less and I think it's important to get the
basic functionality useful for the most common usages.

I'm not sure just how the changes should be designed though -- it would
seem wrong to put them in vfs_mountedon(), and I'm more inclined to
think a call-back to the drivers would be best....

FYI this router of mine is a little i386 (now a Pentium-S 150MHz w/16MB)
machine with a pair of IDE drives (just 245+153MB) and a few Ethernet
cards in it.  It's running a build from my current source tree, which is
now a rather dated version imported from NetBSD-current 2000/09/21.
Examination of more recent code shows no relevant changes that I can
spot though....


Since I've been having troubles making the primary disk boot again since
this episode (I'm booting wd0a:netbsd from floppy right now) I decided
to wipe out sector zero for certain and reset the MBR, etc., keeping a
transcript to show the problem:

First of note that we're running with securelevel=1 and the filesystems
are all mounted:

	# sysctl -a | fgrep secur
	kern.securelevel = 1
	# mount          
	/dev/wd0a on / type ffs (local)
	/dev/wd1a on /var type ffs (local)
	mfs:663 on /tmp type mfs (asynchronous, local, nosuid)
	kernfs on /kern type kernfs (local)
	procfs on /proc type procfs (local)

Now let's demonstrate the problem:

	# dd if=/dev/zero of=/dev/rwd0d count=1
	1+0 records in
	1+0 records out
	512 bytes transferred in 1 secs (512 bytes/sec)

And finally prove that the demonstration "worked":

	# fdisk -i wd0
	fdisk: invalid fdisk partition table found
	NetBSD disklabel disk geometry:
	cylinders: 723 heads: 13 sectors/track: 51 (663 sectors/cylinder)
	
	BIOS disk geometry:
	cylinders: 722 heads: 13 sectors/track: 51 (663 sectors/cylinder)
	
	Do you want to change our idea of what BIOS thinks? [n] n
	
	We haven't written the MBR back to disk yet.  This is your last chance.
	NetBSD disklabel disk geometry:
	cylinders: 723 heads: 13 sectors/track: 51 (663 sectors/cylinder)
	
	BIOS disk geometry:
	cylinders: 722 heads: 13 sectors/track: 51 (663 sectors/cylinder)
	
	Partition table:
	0: <UNUSED>
	1: <UNUSED>
	2: <UNUSED>
	3: <UNUSED>
	Should we write new partition table? [n] y

(and of course even the success of fdisk writing the MBR is another
indication of the danger here!)

After fixing the partition table yet another demonstration of the
problem is possible with installboot(8), the manual page of which also
claims the raw disk cannot be written to "securelevel set to one if the
``boot'' partition is mounted."

	# cd /usr/mdec
	# ./installboot -v biosboot.sym /dev/rwd0a
	biosboot.sym: entry point 0x805c000
	proto bootblock size 49152
	room for 10 filesystem blocks at 0x578
	renamed //boot -> //boot.bak
	Will load 81 blocks.
	dblk: 160560, num: 16
	dblk: 160576, num: 16
	dblk: 160592, num: 16
	dblk: 160608, num: 16
	dblk: 160624, num: 16
	dblk: 162008, num: 1
	installboot: open raw partition RW: Device busy
	renaming //boot.bak -> //boot

Even that works just fine despite my securelevel == 1....

(I do though have a vague memory of not being able to do an installboot
on some machine in multi-user mode once before though....  Maybe that
was FreeBSD though -- their vfs_mountedon() function looks a *lot*
different, though I'm not sure it does anything much different since
they seem to just have a pointer to the corresponding raw device in
every struct vnode, which of course makes its check very simple.)

Just for interest here's the disklabel:

	# disklabel wd0
	# /dev/rwd0d:
	type: unknown
	disk: router
	label: 
	flags:
	bytes/sector: 512
	sectors/track: 51
	tracks/cylinder: 13
	sectors/cylinder: 663
	cylinders: 723
	total sectors: 479349
	rpm: 3600
	interleave: 1
	trackskew: 0
	cylinderskew: 0
	headswitch: 0           # microseconds
	track-to-track seek: 0  # microseconds
	drivedata: 0 
	
	8 partitions:
	#        size   offset     fstype   [fsize bsize   cpg]
	  a:   419240       51     4.2BSD     1024  8192    16   # (Cyl.    0*- 632*)
	  b:    60058   419291       swap                        # (Cyl.  632*- 722)
	  c:   479298       51     unused        0     0         # (Cyl.    0*- 722)
	  d:   479349        0     unused        0     0         # (Cyl.    0 - 722)


(let me know if I should turn this message into a PR....  I did a text
search for "securelevel" in the PR database but came up effectively
empty handed with only closed entries for unrelated problems)

-- 
							Greg A. Woods

+1 416 218-0098      VE3TCP      <gwoods@acm.org>      <robohack!woods>
Planix, Inc. <woods@planix.com>; Secrets of the Weird <woods@weird.com>