Commit 3bc1fa8a authored by Chris Wright's avatar Chris Wright Committed by Linus Torvalds

[PATCH] LSM: remove BSD secure level security module

This code has suffered from broken core design and lack of developer
attention.  Broken security modules are too dangerous to leave around.  It
is time to remove this one.
Signed-off-by: default avatarChris Wright <chrisw@sous-sol.org>
Acked-by: default avatarMichael Halcrow <mhalcrow@us.ibm.com>
Acked-by: default avatarSerge Hallyn <serue@us.ibm.com>
Cc: Davi Arnaut <davi.arnaut@gmail.com>
Acked-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
Acked-by: default avatarJames Morris <jmorris@namei.org>
Acked-by: default avatarAlan Cox <alan@redhat.com>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent cd1c6a48
BSD Secure Levels Linux Security Module
Michael A. Halcrow <mike@halcrow.us>
Introduction
Under the BSD Secure Levels security model, sets of policies are
associated with levels. Levels range from -1 to 2, with -1 being the
weakest and 2 being the strongest. These security policies are
enforced at the kernel level, so not even the superuser is able to
disable or circumvent them. This hardens the machine against attackers
who gain root access to the system.
Levels and Policies
Level -1 (Permanently Insecure):
- Cannot increase the secure level
Level 0 (Insecure):
- Cannot ptrace the init process
Level 1 (Default):
- /dev/mem and /dev/kmem are read-only
- IMMUTABLE and APPEND extended attributes, if set, may not be unset
- Cannot load or unload kernel modules
- Cannot write directly to a mounted block device
- Cannot perform raw I/O operations
- Cannot perform network administrative tasks
- Cannot setuid any file
Level 2 (Secure):
- Cannot decrement the system time
- Cannot write to any block device, whether mounted or not
- Cannot unmount any mounted filesystems
Compilation
To compile the BSD Secure Levels LSM, seclvl.ko, enable the
SECURITY_SECLVL configuration option. This is found under Security
options -> BSD Secure Levels in the kernel configuration menu.
Basic Usage
Once the machine is in a running state, with all the necessary modules
loaded and all the filesystems mounted, you can load the seclvl.ko
module:
# insmod seclvl.ko
The module defaults to secure level 1, except when compiled directly
into the kernel, in which case it defaults to secure level 0. To raise
the secure level to 2, the administrator writes ``2'' to the
seclvl/seclvl file under the sysfs mount point (assumed to be /sys in
these examples):
# echo -n "2" > /sys/seclvl/seclvl
Alternatively, you can initialize the module at secure level 2 with
the initlvl module parameter:
# insmod seclvl.ko initlvl=2
At this point, it is impossible to remove the module or reduce the
secure level. If the administrator wishes to have the option of doing
so, he must provide a module parameter, sha1_passwd, that specifies
the SHA1 hash of the password that can be used to reduce the secure
level to 0.
To generate this SHA1 hash, the administrator can use OpenSSL:
# echo -n "boogabooga" | openssl sha1
abeda4e0f33defa51741217592bf595efb8d289c
In order to use password-instigated secure level reduction, the SHA1
crypto module must be loaded or compiled into the kernel:
# insmod sha1.ko
The administrator can then insmod the seclvl module, including the
SHA1 hash of the password:
# insmod seclvl.ko
sha1_passwd=abeda4e0f33defa51741217592bf595efb8d289c
To reduce the secure level, write the password to seclvl/passwd under
your sysfs mount point:
# echo -n "boogabooga" > /sys/seclvl/passwd
The September 2004 edition of Sys Admin Magazine has an article about
the BSD Secure Levels LSM. I encourage you to refer to that article
for a more in-depth treatment of this security module:
http://www.samag.com/documents/s=9304/sam0409a/0409a.htm
......@@ -93,18 +93,6 @@ config SECURITY_ROOTPLUG
If you are unsure how to answer this question, answer N.
config SECURITY_SECLVL
tristate "BSD Secure Levels"
depends on SECURITY
select CRYPTO
select CRYPTO_SHA1
help
Implements BSD Secure Levels as an LSM. See
<file:Documentation/seclvl.txt> for instructions on how to use this
module.
If you are unsure how to answer this question, answer N.
source security/selinux/Kconfig
endmenu
......
......@@ -16,4 +16,3 @@ obj-$(CONFIG_SECURITY) += security.o dummy.o inode.o
obj-$(CONFIG_SECURITY_SELINUX) += selinux/built-in.o
obj-$(CONFIG_SECURITY_CAPABILITIES) += commoncap.o capability.o
obj-$(CONFIG_SECURITY_ROOTPLUG) += commoncap.o root_plug.o
obj-$(CONFIG_SECURITY_SECLVL) += seclvl.o
/**
* BSD Secure Levels LSM
*
* Maintainers:
* Michael A. Halcrow <mike@halcrow.us>
* Serge Hallyn <hallyn@cs.wm.edu>
*
* Copyright (c) 2001 WireX Communications, Inc <chris@wirex.com>
* Copyright (c) 2001 Greg Kroah-Hartman <greg@kroah.com>
* Copyright (c) 2002 International Business Machines <robb@austin.ibm.com>
* Copyright (c) 2006 Davi E. M. Arnaut <davi.arnaut@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/err.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/security.h>
#include <linux/netlink.h>
#include <linux/fs.h>
#include <linux/namei.h>
#include <linux/mount.h>
#include <linux/capability.h>
#include <linux/time.h>
#include <linux/proc_fs.h>
#include <linux/kobject.h>
#include <linux/crypto.h>
#include <asm/scatterlist.h>
#include <linux/scatterlist.h>
#include <linux/gfp.h>
#include <linux/sysfs.h>
#define SHA1_DIGEST_SIZE 20
/**
* Module parameter that defines the initial secure level.
*
* When built as a module, it defaults to seclvl 1, which is the
* behavior of BSD secure levels. Note that this default behavior
* wrecks havoc on a machine when the seclvl module is compiled into
* the kernel. In that case, we default to seclvl 0.
*/
#ifdef CONFIG_SECURITY_SECLVL_MODULE
static int initlvl = 1;
#else
static int initlvl;
#endif
module_param(initlvl, int, 0);
MODULE_PARM_DESC(initlvl, "Initial secure level (defaults to 1)");
/* Module parameter that defines the verbosity level */
static int verbosity;
module_param(verbosity, int, 0);
MODULE_PARM_DESC(verbosity, "Initial verbosity level (0 or 1; defaults to "
"0, which is Quiet)");
/**
* Optional password which can be passed in to bring seclvl to 0
* (i.e., for halt/reboot). Defaults to NULL (the passwd attribute
* file will not be registered in sysfs).
*
* This gets converted to its SHA1 hash when stored. It's probably
* not a good idea to use this parameter when loading seclvl from a
* script; use sha1_passwd instead.
*/
#define MAX_PASSWD_SIZE 32
static char passwd[MAX_PASSWD_SIZE];
module_param_string(passwd, passwd, sizeof(passwd), 0);
MODULE_PARM_DESC(passwd,
"Plaintext of password that sets seclvl=0 when written to "
"(sysfs mount point)/seclvl/passwd\n");
/**
* SHA1 hashed version of the optional password which can be passed in
* to bring seclvl to 0 (i.e., for halt/reboot). Must be in
* hexadecimal format (40 characters). Defaults to NULL (the passwd
* attribute file will not be registered in sysfs).
*
* Use the sha1sum utility to generate the SHA1 hash of a password:
*
* echo -n "secret" | sha1sum
*/
#define MAX_SHA1_PASSWD 41
static char sha1_passwd[MAX_SHA1_PASSWD];
module_param_string(sha1_passwd, sha1_passwd, sizeof(sha1_passwd), 0);
MODULE_PARM_DESC(sha1_passwd,
"SHA1 hash (40 hexadecimal characters) of password that "
"sets seclvl=0 when plaintext password is written to "
"(sysfs mount point)/seclvl/passwd\n");
static int hideHash = 1;
module_param(hideHash, int, 0);
MODULE_PARM_DESC(hideHash, "When set to 0, reading seclvl/passwd from sysfs "
"will return the SHA1-hashed value of the password that "
"lowers the secure level to 0.\n");
#define MY_NAME "seclvl"
/**
* This time-limits log writes to one per second.
*/
#define seclvl_printk(verb, type, fmt, arg...) \
do { \
if (verbosity >= verb) { \
static unsigned long _prior; \
unsigned long _now = jiffies; \
if ((_now - _prior) > HZ) { \
printk(type "%s: %s: " fmt, \
MY_NAME, __FUNCTION__ , \
## arg); \
_prior = _now; \
} \
} \
} while (0)
/**
* The actual security level. Ranges between -1 and 2 inclusive.
*/
static int seclvl;
/**
* flag to keep track of how we were registered
*/
static int secondary;
/**
* Verifies that the requested secure level is valid, given the current
* secure level.
*/
static int seclvl_sanity(int reqlvl)
{
if ((reqlvl < -1) || (reqlvl > 2)) {
seclvl_printk(1, KERN_WARNING, "Attempt to set seclvl out of "
"range: [%d]\n", reqlvl);
return -EINVAL;
}
if ((seclvl == 0) && (reqlvl == -1))
return 0;
if (reqlvl < seclvl) {
seclvl_printk(1, KERN_WARNING, "Attempt to lower seclvl to "
"[%d]\n", reqlvl);
return -EPERM;
}
return 0;
}
/**
* security level advancement rules:
* Valid levels are -1 through 2, inclusive.
* From -1, stuck. [ in case compiled into kernel ]
* From 0 or above, can only increment.
*/
static void do_seclvl_advance(void *data, u64 val)
{
int ret;
int newlvl = (int)val;
ret = seclvl_sanity(newlvl);
if (ret)
return;
if (newlvl > 2) {
seclvl_printk(1, KERN_WARNING, "Cannot advance to seclvl "
"[%d]\n", newlvl);
return;
}
if (seclvl == -1) {
seclvl_printk(1, KERN_WARNING, "Not allowed to advance to "
"seclvl [%d]\n", seclvl);
return;
}
seclvl = newlvl; /* would it be more "correct" to set *data? */
return;
}
static u64 seclvl_int_get(void *data)
{
return *(int *)data;
}
DEFINE_SIMPLE_ATTRIBUTE(seclvl_file_ops, seclvl_int_get, do_seclvl_advance, "%lld\n");
static unsigned char hashedPassword[SHA1_DIGEST_SIZE];
/**
* Converts a block of plaintext of into its SHA1 hashed value.
*
* It would be nice if crypto had a wrapper to do this for us linear
* people...
*/
static int
plaintext_to_sha1(unsigned char *hash, const char *plaintext, unsigned int len)
{
struct hash_desc desc;
struct scatterlist sg;
int err;
if (len > PAGE_SIZE) {
seclvl_printk(0, KERN_ERR, "Plaintext password too large (%d "
"characters). Largest possible is %lu "
"bytes.\n", len, PAGE_SIZE);
return -EINVAL;
}
desc.tfm = crypto_alloc_hash("sha1", 0, CRYPTO_ALG_ASYNC);
if (IS_ERR(desc.tfm)) {
seclvl_printk(0, KERN_ERR,
"Failed to load transform for SHA1\n");
return -EINVAL;
}
sg_init_one(&sg, (u8 *)plaintext, len);
desc.flags = CRYPTO_TFM_REQ_MAY_SLEEP;
err = crypto_hash_digest(&desc, &sg, len, hash);
crypto_free_hash(desc.tfm);
return err;
}
/**
* Called whenever the user writes to the sysfs passwd handle to this kernel
* object. It hashes the password and compares the hashed results.
*/
static ssize_t
passwd_write_file(struct file * file, const char __user * buf,
size_t count, loff_t *ppos)
{
char *p;
int len;
unsigned char tmp[SHA1_DIGEST_SIZE];
if (!*passwd && !*sha1_passwd) {
seclvl_printk(0, KERN_ERR, "Attempt to password-unlock the "
"seclvl module, but neither a plain text "
"password nor a SHA1 hashed password was "
"passed in as a module parameter! This is a "
"bug, since it should not be possible to be in "
"this part of the module; please tell a "
"maintainer about this event.\n");
return -EINVAL;
}
if (count >= PAGE_SIZE)
return -EINVAL;
if (*ppos != 0)
return -EINVAL;
p = kmalloc(count, GFP_KERNEL);
if (!p)
return -ENOMEM;
len = -EFAULT;
if (copy_from_user(p, buf, count))
goto out;
len = count;
/* ``echo "secret" > seclvl/passwd'' includes a newline */
if (p[len - 1] == '\n')
len--;
/* Hash the password, then compare the hashed values */
if ((len = plaintext_to_sha1(tmp, p, len))) {
seclvl_printk(0, KERN_ERR, "Error hashing password: rc = "
"[%d]\n", len);
goto out;
}
len = -EPERM;
if (memcmp(hashedPassword, tmp, SHA1_DIGEST_SIZE))
goto out;
seclvl_printk(0, KERN_INFO,
"Password accepted; seclvl reduced to 0.\n");
seclvl = 0;
len = count;
out:
kfree (p);
return len;
}
static struct file_operations passwd_file_ops = {
.write = passwd_write_file,
};
/**
* Explicitely disallow ptrace'ing the init process.
*/
static int seclvl_ptrace(struct task_struct *parent, struct task_struct *child)
{
if (seclvl >= 0 && child->pid == 1) {
seclvl_printk(1, KERN_WARNING, "Attempt to ptrace "
"the init process dissallowed in "
"secure level %d\n", seclvl);
return -EPERM;
}
return 0;
}
/**
* Capability checks for seclvl. The majority of the policy
* enforcement for seclvl takes place here.
*/
static int seclvl_capable(struct task_struct *tsk, int cap)
{
int rc = 0;
/* init can do anything it wants */
if (tsk->pid == 1)
return 0;
if (seclvl > 0) {
rc = -EPERM;
if (cap == CAP_LINUX_IMMUTABLE)
seclvl_printk(1, KERN_WARNING, "Attempt to modify "
"the IMMUTABLE and/or APPEND extended "
"attribute on a file with the IMMUTABLE "
"and/or APPEND extended attribute set "
"denied in seclvl [%d]\n", seclvl);
else if (cap == CAP_SYS_RAWIO)
seclvl_printk(1, KERN_WARNING, "Attempt to perform "
"raw I/O while in secure level [%d] "
"denied\n", seclvl);
else if (cap == CAP_NET_ADMIN)
seclvl_printk(1, KERN_WARNING, "Attempt to perform "
"network administrative task while "
"in secure level [%d] denied\n", seclvl);
else if (cap == CAP_SETUID)
seclvl_printk(1, KERN_WARNING, "Attempt to setuid "
"while in secure level [%d] denied\n",
seclvl);
else if (cap == CAP_SETGID)
seclvl_printk(1, KERN_WARNING, "Attempt to setgid "
"while in secure level [%d] denied\n",
seclvl);
else if (cap == CAP_SYS_MODULE)
seclvl_printk(1, KERN_WARNING, "Attempt to perform "
"a module operation while in secure "
"level [%d] denied\n", seclvl);
else
rc = 0;
}
if (!rc) {
if (!(cap_is_fs_cap(cap) ? tsk->fsuid == 0 : tsk->euid == 0))
rc = -EPERM;
}
if (rc)
seclvl_printk(1, KERN_WARNING, "Capability denied\n");
return rc;
}
/**
* Disallow reversing the clock in seclvl > 1
*/
static int seclvl_settime(struct timespec *tv, struct timezone *tz)
{
if (tv && seclvl > 1) {
struct timespec now;
now = current_kernel_time();
if (tv->tv_sec < now.tv_sec ||
(tv->tv_sec == now.tv_sec && tv->tv_nsec < now.tv_nsec)) {
seclvl_printk(1, KERN_WARNING, "Attempt to decrement "
"time in secure level %d denied: "
"current->pid = [%d], "
"current->group_leader->pid = [%d]\n",
seclvl, current->pid,
current->group_leader->pid);
return -EPERM;
} /* if attempt to decrement time */
} /* if seclvl > 1 */
return 0;
}
/* claim the blockdev to exclude mounters, release on file close */
static int seclvl_bd_claim(struct inode *inode)
{
int holder;
struct block_device *bdev = NULL;
dev_t dev = inode->i_rdev;
bdev = open_by_devnum(dev, FMODE_WRITE);
if (bdev) {
if (bd_claim(bdev, &holder)) {
blkdev_put(bdev);
return -EPERM;
}
/* claimed, mark it to release on close */
inode->i_security = current;
}
return 0;
}
/* release the blockdev if you claimed it */
static void seclvl_bd_release(struct inode *inode)
{
if (inode && S_ISBLK(inode->i_mode) && inode->i_security == current) {
struct block_device *bdev = inode->i_bdev;
if (bdev) {
bd_release(bdev);
blkdev_put(bdev);
inode->i_security = NULL;
}
}
}
/**
* Security for writes to block devices is regulated by this seclvl
* function. Deny all writes to block devices in seclvl 2. In
* seclvl 1, we only deny writes to *mounted* block devices.
*/
static int
seclvl_inode_permission(struct inode *inode, int mask, struct nameidata *nd)
{
if (current->pid != 1 && S_ISBLK(inode->i_mode) && (mask & MAY_WRITE)) {
switch (seclvl) {
case 2:
seclvl_printk(1, KERN_WARNING, "Write to block device "
"denied in secure level [%d]\n", seclvl);
return -EPERM;
case 1:
if (seclvl_bd_claim(inode)) {
seclvl_printk(1, KERN_WARNING,
"Write to mounted block device "
"denied in secure level [%d]\n",
seclvl);
return -EPERM;
}
}
}
return 0;
}
/**
* The SUID and SGID bits cannot be set in seclvl >= 1
*/
static int seclvl_inode_setattr(struct dentry *dentry, struct iattr *iattr)
{
if (seclvl > 0) {
if (iattr->ia_valid & ATTR_MODE)
if (iattr->ia_mode & S_ISUID ||
iattr->ia_mode & S_ISGID) {
seclvl_printk(1, KERN_WARNING, "Attempt to "
"modify SUID or SGID bit "
"denied in seclvl [%d]\n",
seclvl);
return -EPERM;
}
}
return 0;
}
/* release busied block devices */
static void seclvl_file_free_security(struct file *filp)
{
struct dentry *dentry = filp->f_dentry;
if (dentry)
seclvl_bd_release(dentry->d_inode);
}
/**
* Cannot unmount in secure level 2
*/
static int seclvl_umount(struct vfsmount *mnt, int flags)
{
if (current->pid != 1 && seclvl == 2) {
seclvl_printk(1, KERN_WARNING, "Attempt to unmount in secure "
"level %d\n", seclvl);
return -EPERM;
}
return 0;
}
static struct security_operations seclvl_ops = {
.ptrace = seclvl_ptrace,
.capable = seclvl_capable,
.inode_permission = seclvl_inode_permission,
.inode_setattr = seclvl_inode_setattr,
.file_free_security = seclvl_file_free_security,
.settime = seclvl_settime,
.sb_umount = seclvl_umount,
};
/**
* Process the password-related module parameters
*/
static int processPassword(void)
{
int rc = 0;
if (*passwd) {
char *p;
if (*sha1_passwd) {
seclvl_printk(0, KERN_ERR, "Error: Both "
"passwd and sha1_passwd "
"were set, but they are mutually "
"exclusive.\n");
return -EINVAL;
}
p = kstrdup(passwd, GFP_KERNEL);
if (p == NULL)
return -ENOMEM;
if ((rc = plaintext_to_sha1(hashedPassword, p, strlen(p))))
seclvl_printk(0, KERN_ERR, "Error: SHA1 support not "
"in kernel\n");
kfree (p);
/* All static data goes to the BSS, which zero's the
* plaintext password out for us. */
} else if (*sha1_passwd) { // Base 16
int i;
i = strlen(sha1_passwd);
if (i != (SHA1_DIGEST_SIZE * 2)) {
seclvl_printk(0, KERN_ERR, "Received [%d] bytes; "
"expected [%d] for the hexadecimal "
"representation of the SHA1 hash of "
"the password.\n",
i, (SHA1_DIGEST_SIZE * 2));
return -EINVAL;
}
while ((i -= 2) + 2) {
unsigned char tmp;
tmp = sha1_passwd[i + 2];
sha1_passwd[i + 2] = '\0';
hashedPassword[i / 2] = (unsigned char)
simple_strtol(&sha1_passwd[i], NULL, 16);
sha1_passwd[i + 2] = tmp;
}
}
return rc;
}
/**
* securityfs registrations
*/
struct dentry *dir_ino, *seclvl_ino, *passwd_ino;
static int seclvlfs_register(void)
{
int rc = 0;
dir_ino = securityfs_create_dir("seclvl", NULL);
if (IS_ERR(dir_ino))
return PTR_ERR(dir_ino);
seclvl_ino = securityfs_create_file("seclvl", S_IRUGO | S_IWUSR,
dir_ino, &seclvl, &seclvl_file_ops);
if (IS_ERR(seclvl_ino)) {
rc = PTR_ERR(seclvl_ino);
goto out_deldir;
}
if (*passwd || *sha1_passwd) {
passwd_ino = securityfs_create_file("passwd", S_IRUGO | S_IWUSR,
dir_ino, NULL, &passwd_file_ops);
if (IS_ERR(passwd_ino)) {
rc = PTR_ERR(passwd_ino);
goto out_delf;
}
}
return rc;
out_delf:
securityfs_remove(seclvl_ino);
out_deldir:
securityfs_remove(dir_ino);
return rc;
}
static void seclvlfs_unregister(void)
{
securityfs_remove(seclvl_ino);
if (*passwd || *sha1_passwd)
securityfs_remove(passwd_ino);
securityfs_remove(dir_ino);
}
/**
* Initialize the seclvl module.
*/
static int __init seclvl_init(void)
{
int rc = 0;
static char once;
if (verbosity < 0 || verbosity > 1) {
printk(KERN_ERR "Error: bad verbosity [%d]; only 0 or 1 "
"are valid values\n", verbosity);
rc = -EINVAL;
goto exit;
}
if (initlvl < -1 || initlvl > 2) {
seclvl_printk(0, KERN_ERR, "Error: bad initial securelevel "
"[%d].\n", initlvl);
rc = -EINVAL;
goto exit;
}
seclvl = initlvl;
if ((rc = processPassword())) {
seclvl_printk(0, KERN_ERR, "Error processing the password "
"module parameter(s): rc = [%d]\n", rc);
goto exit;
}
if ((rc = seclvlfs_register())) {
seclvl_printk(0, KERN_ERR, "Error registering with sysfs\n");
goto exit;
}
/* register ourselves with the security framework */
if (register_security(&seclvl_ops)) {
seclvl_printk(0, KERN_ERR,
"seclvl: Failure registering with the "
"kernel.\n");
/* try registering with primary module */
rc = mod_reg_security(MY_NAME, &seclvl_ops);
if (rc) {
seclvl_printk(0, KERN_ERR, "seclvl: Failure "
"registering with primary security "
"module.\n");
seclvlfs_unregister();
goto exit;
} /* if primary module registered */
secondary = 1;
} /* if we registered ourselves with the security framework */
seclvl_printk(0, KERN_INFO, "seclvl: Successfully initialized.\n");
if (once) {
once = 1;
seclvl_printk(0, KERN_INFO, "seclvl is going away. It has been "
"buggy for ages. Also, be warned that "
"Securelevels are useless.");
}
exit:
if (rc)
printk(KERN_ERR "seclvl: Error during initialization: rc = "
"[%d]\n", rc);
return rc;
}
/**
* Remove the seclvl module.
*/
static void __exit seclvl_exit(void)
{
seclvlfs_unregister();
if (secondary)
mod_unreg_security(MY_NAME, &seclvl_ops);
else if (unregister_security(&seclvl_ops))
seclvl_printk(0, KERN_INFO,
"seclvl: Failure unregistering with the "
"kernel\n");
}
module_init(seclvl_init);
module_exit(seclvl_exit);
MODULE_AUTHOR("Michael A. Halcrow <mike@halcrow.us>");
MODULE_DESCRIPTION("LSM implementation of the BSD Secure Levels");
MODULE_LICENSE("GPL");
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment