From robert@cyrus.watson.org Sun Apr 19 01:51:22 1998 Date: Tue, 7 Apr 1998 17:29:10 -0400 (EDT) From: Robert Watson Reply-To: Robert Watson To: security@freebsd.org Subject: securelevels and more liberal use of schg on system files (fwd) The following discussion of securelevels is the result of some experimentation and largely consists of brainstorming, but might make for some interesting discussion (if other people find it interesting :). BSD/OS and others make use of securelevels in their production releases to prevent time rewinds, etc. It may be desirable that FreeBSD do so in the future. Currently, however, securelevels do little if anything to actually increase the security of a system. I think this can be improved through some careful thinking out of the issues, and would like to see this happen in FreeBSD. :) All experimentation was performed on 2.2-STABLE a few days ago. -- Through securelevels, FreeBSD can be hardened against a root compromise -- securelevel >= 2 prevents (short list): 1) Modification of init that might result in the securelevel being lowered (via ptrace, procfs) 2) Writing to /dev/kern-type files where kernel modifications could be used to lower the securelevel 3) Prevent writing to character and block devices for mountable hard drives, etc, that might be used to modify the kernel on disk, etc. 4) securelevel can only be raised, never lowered using sysctl 5) the schg, sappnd and sunlnk flags are now enforced for uid 0 (also, other behavior depending on the file system, syscalls, etc) Additionally, make installworld applies the schg flag to a few key files to prevent modification that might result on problems going to single-user mode, or next boot: /kernel # technically during install of kernel /bin/rcp /sbin/init /usr/sbin/sliplogin /usr/bin/chfn /usr/bin/chpass /usr/bin/chsh /usr/bin/crontab /usr/bin/login /usr/bin/man /usr/bin/passwd /usr/bin/rlogin /usr/bin/rsh /usr/bin/su /usr/bin/ypchfn /usr/bin/ypchpass /usr/bin/ypchsh /usr/bin/yppasswd /usr/lib/libc.so.3.1 /usr/lib/libcipher.so.2.0 /usr/lib/libdescrypt.so.2.0 /usr/libexec/ld.so /usr/libexec/mail.local There may be others, but I have not found them in my cursory search. It is now useful to observe how it is we get into a non- -1 securelevel. The man page init(8) makes the following recommendation: If the security level is initially -1, then init leaves it unchanged. Otherwise, init arranges to run the system in level 0 mode while single user and in level 1 mode while multiuser. If level 2 mode is desired while running multiuser, it can be set while single user, e.g., in the startup script /etc/rc, using sysctl(8). Note that it is not init(8) itself that performs the sysctl activity, but rather the rc(8) script. In a sense, it cannot be init(8), as the rc(8) script later runs fsck(8) to clean the file systems, and it requires write access to the block devices. However, this does leave us with a few problems. Neither /etc/rc nor /usr/sbin/sysctl are protected by schg. For that matter, neither is fsck, or any number of utilities used by rc(8) prior to being able to raise the securelevel. While some of the above files clearly deserve to be schg, there do appear to be a few interlopers (passwd? :) on the domain of the carefully protected. thithle:/# sysctl kern.securelevel kern.securelevel: 2 thithle:/# cp /bin/echo /usr/sbin/sysctl or better, thithle:/# cp ~hacked/rc /etc/rc Additionally, while we notice that the files are protected against modification (and links to them), the directories are *NOT*. /sbin/init is schg, but the directory /sbin is not! Similarly with all of the other non-/ files (i.e., all but /kernel). To replace init(8) at the next boot, I can happily do the following: thithle:/# mv /sbin /sbinsbin thithle:/# mkdir /sbin thithle:/# cp /sbinsbin/* /sbin thithle:/# cp ~hacked/myinit /sbin/init thithle:/# reboot schg does prevent modifications to init(8) that might result in paging in replacement pages of text into the current init, but not against actions over reboot. One response to the problem of key binaries in / and /etc is to mount the root partition (and /etc) as read-only devices instead of read-write. However, securelevel does not prevent the remounting of devices as read-write from an initial read-only state: thithle:/# grep wd0s1a /etc/fstab /dev/wd0s1a / ufs ro 1 1 thithle:/# mount /dev/wd0s1a on / (local, read-only) ... thithle:/# mount -o rwd / thithle:/# mount /dev/wd0s1a on / (local) ... thithle:/# So there are some fairly severe problems with the current behavior of FreeBSD with securelevel in use. Just to sumarize: 1) The set of files installed with schg is not sufficient for any environment where file systems containing binaries are writable by software (i.e., not a CD-ROM or a write-protected floppy). Even though some of the required files are protected, their directories are not protected in the default install. Default installs probably do not benefit from having so many key directories (/usr/lib, /sbin, /etc) be schg, but if securelevels are to be used, it must be published that they cannot just be "turned on" as described in init(8). 2) A read-only mount can be converted into a read-write mount without any prevention of the kernel. With the use of chroot(8), life can be made much harder for the uid 0 user when it comes to modifying key system files. However, through creation of device nodes (which is permitted in securelevel>=2) in mfs partitions, it may be possible to break out and remount / read-write. The current policy for mounting/umounting partitions in high securelevels may not be adequate, given the desire to protect key system directories against attacks over a reboot. 3) Unmentioned above, the init search path in the kernel actual attempts to run a series of possible programs: init_main.c: /* * List of paths to try when searching for "init". */ static char *initpaths[] = { "/sbin/init", "/sbin/oinit", "/sbin/init.bak", "/stand/sysinstall", NULL, }; Even the ability to move /sbin/init out of the way in any form is dangerous -- let alone replacing it. While the kernel is effective at protecting against its securelevel being lowered in the course of a particular boot/running time, FreeBSD is currently extremely succeptible to manipulation of its configuration files and key binaries. Similarly, the possibility of embedding a trojan in any number of standard UNIX utilities (inclding /stand/sysinstall) makes the system entirely untrustworthy even in single-user mode. The promise of schg/securelevels can only be kept if all instances of binaries executed while in a lower securelevel (i.e., in boot and in single-user mode) are protected against execution in lower securelevels (multi-user mode). Recommendations: Reconsider the ability to mount anything but nodev/nosuid partitions under securelevel>=2. Give thought to the issue of readonly mounts, and possibly how to protect against remounting readwrite (especially for /). Perhaps provide a fascistflags utility that makes all key binaries and directories schg. Also, notes indicating that the current securelevel behavior recommended in init(8) is not sufficient. As a side note, it is interesting that make buildworld installs files as schg in the binary tree it builds. This seems highly inappropriate -- the flags should only be set on an installworld! Otherwise make clean will not work on a securelevel>=1 system, so builds cannot be repeated in the same tree without dropping to a lower securelevel to clean the tree. installworld clearly requires a low securelevel, but often build machines are not the same as user machines. I am also interested to know why such an interesting array of commands is schg -- for example, there is no reason why passwd(1) should be schg that I can think of. Either way it aquires root access, or uses NIS. A uid-0 user could easily compile there own. Presumably this is to allow passwords to be changed safely after a penetration; this idea is probably bogus. Incidently, init(8) attempts to lower the securelevel when going out of multi-user mode on a shutdown. It cannot succeed in doing this with the current implementation, as it is not possible to lower the securelevel. However, if it were, we would also be in bad shape -- processes do not necessarily die on the transition to single user mode (if blocked on a resource, for example) -- if init(8) successfully lowered the securelevel, this process would now retain uid 0, but be running in a normal securelevel, allowing it to modify schg files, etc. Since this doesn't work, this is actually OK. init(8) does have some misconceptions, though :-). Robert N Watson ---- Carnegie Mellon University http://www.cmu.edu/ Trusted Information Systems http://www.tis.com/ SafePort Network Services http://www.safeport.com/ robert@fledge.watson.org http://www.watson.org/~robert/