aboutsummaryrefslogblamecommitdiffstats
path: root/security/seclvl.c
blob: 96b1f2122f67bdac673a18fab792ea56e8d236ab (plain) (tree)
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747




























































































































































                                                                             
                                                                         







                                                                             
                                                                  



























































































































































                                                                              
                                                                 






































































































































































































































































































































































































































                                                                                            
/**
 * 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>
 *
 *	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/config.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/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)

/**
 * kobject stuff
 */

struct subsystem seclvl_subsys;

struct seclvl_obj {
	char *name;
	struct list_head slot_list;
	struct kobject kobj;
};

/**
 * There is a seclvl_attribute struct for each file in sysfs.
 *
 * In our case, we have one of these structs for "passwd" and another
 * for "seclvl".
 */
struct seclvl_attribute {
	struct attribute attr;
	ssize_t(*show) (struct seclvl_obj *, char *);
	ssize_t(*store) (struct seclvl_obj *, const char *, size_t);
};

/**
 * When this function is called, one of the files in sysfs is being
 * written to.  attribute->store is a function pointer to whatever the
 * struct seclvl_attribute store function pointer points to.  It is
 * unique for "passwd" and "seclvl".
 */
static ssize_t
seclvl_attr_store(struct kobject *kobj,
		  struct attribute *attr, const char *buf, size_t len)
{
	struct seclvl_obj *obj = container_of(kobj, struct seclvl_obj, kobj);
	struct seclvl_attribute *attribute =
	    container_of(attr, struct seclvl_attribute, attr);
	return attribute->store ? attribute->store(obj, buf, len) : -EIO;
}

static ssize_t
seclvl_attr_show(struct kobject *kobj, struct attribute *attr, char *buf)
{
	struct seclvl_obj *obj = container_of(kobj, struct seclvl_obj, kobj);
	struct seclvl_attribute *attribute =
	    container_of(attr, struct seclvl_attribute, attr);
	return attribute->show ? attribute->show(obj, buf) : -EIO;
}

/**
 * Callback function pointers for show and store
 */
static struct sysfs_ops seclvlfs_sysfs_ops = {
	.show = seclvl_attr_show,
	.store = seclvl_attr_store,
};

static struct kobj_type seclvl_ktype = {
	.sysfs_ops = &seclvlfs_sysfs_ops
};

decl_subsys(seclvl, &seclvl_ktype, NULL);

/**
 * 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;
}

/**
 * Called whenever the user reads the sysfs handle to this kernel
 * object
 */
static ssize_t seclvl_read_file(struct seclvl_obj *obj, char *buff)
{
	return snprintf(buff, PAGE_SIZE, "%d\n", seclvl);
}

/**
 * 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 int do_seclvl_advance(int newlvl)
{
	if (newlvl <= seclvl) {
		seclvl_printk(1, KERN_WARNING, "Cannot advance to seclvl "
			      "[%d]\n", newlvl);
		return -EINVAL;
	}
	if (newlvl > 2) {
		seclvl_printk(1, KERN_WARNING, "Cannot advance to seclvl "
			      "[%d]\n", newlvl);
		return -EINVAL;
	}
	if (seclvl == -1) {
		seclvl_printk(1, KERN_WARNING, "Not allowed to advance to "
			      "seclvl [%d]\n", seclvl);
		return -EPERM;
	}
	seclvl = newlvl;
	return 0;
}

/**
 * Called whenever the user writes to the sysfs handle to this kernel
 * object (seclvl/seclvl).  It expects a single-digit number.
 */
static ssize_t
seclvl_write_file(struct seclvl_obj *obj, const char *buff, size_t count)
{
	unsigned long val;
	if (count > 2 || (count == 2 && buff[1] != '\n')) {
		seclvl_printk(1, KERN_WARNING, "Invalid value passed to "
			      "seclvl: [%s]\n", buff);
		return -EINVAL;
	}
	val = buff[0] - 48;
	if (seclvl_sanity(val)) {
		seclvl_printk(1, KERN_WARNING, "Illegal secure level "
			      "requested: [%d]\n", (int)val);
		return -EPERM;
	}
	if (do_seclvl_advance(val)) {
		seclvl_printk(0, KERN_ERR, "Failure advancing security level "
			      "to %lu\n", val);
	}
	return count;
}

/* Generate sysfs_attr_seclvl */
static struct seclvl_attribute sysfs_attr_seclvl =
__ATTR(seclvl, (S_IFREG | S_IRUGO | S_IWUSR), seclvl_read_file,
       seclvl_write_file);

static unsigned char hashedPassword[SHA1_DIGEST_SIZE];

/**
 * Called whenever the user reads the sysfs passwd handle.
 */
static ssize_t seclvl_read_passwd(struct seclvl_obj *obj, char *buff)
{
	/* So just how good *is* your password? :-) */
	char tmp[3];
	int i = 0;
	buff[0] = '\0';
	if (hideHash) {
		/* Security through obscurity */
		return 0;
	}
	while (i < SHA1_DIGEST_SIZE) {
		snprintf(tmp, 3, "%02x", hashedPassword[i]);
		strncat(buff, tmp, 2);
		i++;
	}
	strcat(buff, "\n");
	return ((SHA1_DIGEST_SIZE * 2) + 1);
}

/**
 * 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, int len)
{
	char *pgVirtAddr;
	struct crypto_tfm *tfm;
	struct scatterlist sg[1];
	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 -ENOMEM;
	}
	tfm = crypto_alloc_tfm("sha1", CRYPTO_TFM_REQ_MAY_SLEEP);
	if (tfm == NULL) {
		seclvl_printk(0, KERN_ERR,
			      "Failed to load transform for SHA1\n");
		return -ENOSYS;
	}
	// Just get a new page; don't play around with page boundaries
	// and scatterlists.
	pgVirtAddr = (char *)__get_free_page(GFP_KERNEL);
	sg[0].page = virt_to_page(pgVirtAddr);
	sg[0].offset = 0;
	sg[0].length = len;
	strncpy(pgVirtAddr, plaintext, len);
	crypto_digest_init(tfm);
	crypto_digest_update(tfm, sg, 1);
	crypto_digest_final(tfm, hash);
	crypto_free_tfm(tfm);
	free_page((unsigned long)pgVirtAddr);
	return 0;
}

/**
 * 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
seclvl_write_passwd(struct seclvl_obj *obj, const char *buff, size_t count)
{
	int i;
	unsigned char tmp[SHA1_DIGEST_SIZE];
	int rc;
	int len;
	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;
	}
	len = strlen(buff);
	/* ``echo "secret" > seclvl/passwd'' includes a newline */
	if (buff[len - 1] == '\n') {
		len--;
	}
	/* Hash the password, then compare the hashed values */
	if ((rc = plaintext_to_sha1(tmp, buff, len))) {
		seclvl_printk(0, KERN_ERR, "Error hashing password: rc = "
			      "[%d]\n", rc);
		return rc;
	}
	for (i = 0; i < SHA1_DIGEST_SIZE; i++) {
		if (hashedPassword[i] != tmp[i]) {
			return -EPERM;
		}
	}
	seclvl_printk(0, KERN_INFO,
		      "Password accepted; seclvl reduced to 0.\n");
	seclvl = 0;
	return count;
}

/* Generate sysfs_attr_passwd */
static struct seclvl_attribute sysfs_attr_passwd =
__ATTR(passwd, (S_IFREG | S_IRUGO | S_IWUSR), seclvl_read_passwd,
       seclvl_write_passwd);

/**
 * Explicitely disallow ptrace'ing the init process.
 */
static int seclvl_ptrace(struct task_struct *parent, struct task_struct *child)
{
	if (seclvl >= 0) {
		if (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)
{
	/* init can do anything it wants */
	if (tsk->pid == 1)
		return 0;

	switch (seclvl) {
	case 2:
		/* fall through */
	case 1:
		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);
			return -EPERM;
		} else if (cap == CAP_SYS_RAWIO) {	// Somewhat broad...
			seclvl_printk(1, KERN_WARNING, "Attempt to perform "
				      "raw I/O while in secure level [%d] "
				      "denied\n", seclvl);
			return -EPERM;
		} 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);
			return -EPERM;
		} else if (cap == CAP_SETUID) {
			seclvl_printk(1, KERN_WARNING, "Attempt to setuid "
				      "while in secure level [%d] denied\n",
				      seclvl);
			return -EPERM;
		} 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);
			return -EPERM;
		}
		break;
	default:
		break;
	}
	/* from dummy.c */
	if (cap_is_fs_cap(cap) ? tsk->fsuid == 0 : tsk->euid == 0)
		return 0;	/* capability granted */
	seclvl_printk(1, KERN_WARNING, "Capability denied\n");
	return -EPERM;		/* capability denied */
}

/**
 * Disallow reversing the clock in seclvl > 1
 */
static int seclvl_settime(struct timespec *tv, struct timezone *tz)
{
	struct timespec now;
	if (seclvl > 1) {
		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;
	struct inode *inode = NULL;

	if (dentry) {
		inode = dentry->d_inode;
		seclvl_bd_release(inode);
	}
}

/**
 * Cannot unmount in secure level 2
 */
static int seclvl_umount(struct vfsmount *mnt, int flags)
{
	if (current->pid == 1) {
		return 0;
	}
	if (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;
	hashedPassword[0] = '\0';
	if (*passwd) {
		if (*sha1_passwd) {
			seclvl_printk(0, KERN_ERR, "Error: Both "
				      "passwd and sha1_passwd "
				      "were set, but they are mutually "
				      "exclusive.\n");
			return -EINVAL;
		}
		if ((rc = plaintext_to_sha1(hashedPassword, passwd,
					    strlen(passwd)))) {
			seclvl_printk(0, KERN_ERR, "Error: SHA1 support not "
				      "in kernel\n");
			return rc;
		}
		/* 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 0;
}

/**
 * Sysfs registrations
 */
static int doSysfsRegistrations(void)
{
	int rc = 0;
	if ((rc = subsystem_register(&seclvl_subsys))) {
		seclvl_printk(0, KERN_WARNING,
			      "Error [%d] registering seclvl subsystem\n", rc);
		return rc;
	}
	sysfs_create_file(&seclvl_subsys.kset.kobj, &sysfs_attr_seclvl.attr);
	if (*passwd || *sha1_passwd) {
		sysfs_create_file(&seclvl_subsys.kset.kobj,
				  &sysfs_attr_passwd.attr);
	}
	return 0;
}

/**
 * Initialize the seclvl module.
 */
static int __init seclvl_init(void)
{
	int rc = 0;
	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;
	}
	sysfs_attr_seclvl.attr.owner = THIS_MODULE;
	sysfs_attr_passwd.attr.owner = THIS_MODULE;
	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;
	}
	/* 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");
			goto exit;
		}		/* if primary module registered */
		secondary = 1;
	}			/* if we registered ourselves with the security framework */
	if ((rc = doSysfsRegistrations())) {
		seclvl_printk(0, KERN_ERR, "Error registering with sysfs\n");
		goto exit;
	}
	seclvl_printk(0, KERN_INFO, "seclvl: Successfully initialized.\n");
 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)
{
	sysfs_remove_file(&seclvl_subsys.kset.kobj, &sysfs_attr_seclvl.attr);
	if (*passwd || *sha1_passwd) {
		sysfs_remove_file(&seclvl_subsys.kset.kobj,
				  &sysfs_attr_passwd.attr);
	}
	subsystem_unregister(&seclvl_subsys);
	if (secondary == 1) {
		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");