[PATCH] Check files' signatures before doing suid/sgid [2/4]

From: Alexander Wuerstlein
Date: Fri Jun 22 2007 - 14:25:54 EST


Modified task_struct to hold a 'signed flag' which is set on exec(), inherited
on fork() and checked during exec before giving the new process suid/sgid
privileges.

sns.c contains our helper functions to verify the signatures.
sns_secret_key.dat contains the 'secret key' which is used for HMAC.

Signed-off-by: Johannes Schlumberger <spjsschl@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx>
---
fs/exec.c | 19 +++++++-
include/linux/Kbuild | 2 +
include/linux/sched.h | 3 +
include/linux/sns.h | 3 +
kernel/fork.c | 6 +++
security/Kconfig | 28 ++++++++++++
security/Makefile | 1 +
security/sns.c | 104 +++++++++++++++++++++++++++++++++++++++++++
security/sns_secret_key.dat | 5 ++
9 files changed, 169 insertions(+), 2 deletions(-)
create mode 100644 include/linux/sns.h
create mode 100644 security/sns.c
create mode 100644 security/sns_secret_key.dat

diff --git a/fs/exec.c b/fs/exec.c
index f20561f..5dfa406 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -51,6 +51,9 @@
#include <linux/cn_proc.h>
#include <linux/audit.h>
#include <linux/signalfd.h>
+#ifdef CONFIG_SNS_SIGNED
+#include <linux/sns.h>
+#endif

#include <asm/uaccess.h>
#include <asm/mmu_context.h>
@@ -928,13 +931,21 @@ int prepare_binprm(struct linux_binprm *bprm)
mode = inode->i_mode;
if (bprm->file->f_op == NULL)
return -EACCES;
+#ifdef CONFIG_SNS_SIGNED
+ if (mode & S_ISUID)
+ current->sns_valid_sig = sns_signature_valid(bprm->file);
+#endif

bprm->e_uid = current->euid;
bprm->e_gid = current->egid;

if(!(bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID)) {
/* Set-uid? */
- if (mode & S_ISUID) {
+#ifdef CONFIG_SNS_SIGNED_SETUID
+ if (mode & S_ISUID && current->sns_valid_sig) {
+#else
+ if (mode & S_ISUID) {
+#endif /*SNS_SIGNED_SETUID*/
current->personality &= ~PER_CLEAR_ON_SETID;
bprm->e_uid = inode->i_uid;
}
@@ -945,7 +956,11 @@ int prepare_binprm(struct linux_binprm *bprm)
* is a candidate for mandatory locking, not a setgid
* executable.
*/
- if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) {
+#ifdef CONFIG_SNS_SIGNED_SETGID
+ if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP) && current->sns_valid_sig) {
+#else
+ if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) {
+#endif /*SNS_SIGNED_SETGID*/
current->personality &= ~PER_CLEAR_ON_SETID;
bprm->e_gid = inode->i_gid;
}
diff --git a/include/linux/Kbuild b/include/linux/Kbuild
index f317c27..16df5f0 100644
--- a/include/linux/Kbuild
+++ b/include/linux/Kbuild
@@ -159,6 +159,7 @@ header-y += videotext.h
header-y += vt.h
header-y += wireless.h
header-y += x25.h
+header-y += sns.h

unifdef-y += acct.h
unifdef-y += adb.h
@@ -347,5 +348,6 @@ unifdef-y += watchdog.h
unifdef-y += wireless.h
unifdef-y += xattr.h
unifdef-y += xfrm.h
+unifdef-y += sns.h

objhdr-y += version.h
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 693f0e6..36c58d6 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -1076,6 +1076,9 @@ struct task_struct {
#ifdef CONFIG_FAULT_INJECTION
int make_it_fail;
#endif
+#ifdef CONFIG_SNS_SIGNED
+ int sns_valid_sig;
+#endif
};

static inline pid_t process_group(struct task_struct *tsk)
diff --git a/include/linux/sns.h b/include/linux/sns.h
new file mode 100644
index 0000000..ad15e4b
--- /dev/null
+++ b/include/linux/sns.h
@@ -0,0 +1,3 @@
+#ifdef CONFIG_SNS_SIGNED
+int sns_signature_valid(struct file *);
+#endif
diff --git a/kernel/fork.c b/kernel/fork.c
index 73ad5cd..c12cf61 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -156,6 +156,9 @@ void __init fork_init(unsigned long mempages)
init_task.signal->rlim[RLIMIT_NPROC].rlim_max = max_threads/2;
init_task.signal->rlim[RLIMIT_SIGPENDING] =
init_task.signal->rlim[RLIMIT_NPROC];
+#ifdef CONFIG_SNS_SIGNED
+ init_task.sns_valid_sig = 0;
+#endif
}

static struct task_struct *dup_task_struct(struct task_struct *orig)
@@ -182,6 +185,9 @@ static struct task_struct *dup_task_struct(struct task_struct *orig)
#ifdef CONFIG_CC_STACKPROTECTOR
tsk->stack_canary = get_random_int();
#endif
+#ifdef CONFIG_SNS_SIGNED
+ tsk->sns_valid_sig = orig->sns_valid_sig;
+#endif

/* One for us, one for whoever does the "release_task()" (usually parent) */
atomic_set(&tsk->usage,2);
diff --git a/security/Kconfig b/security/Kconfig
index 460e5c9..bfaace7 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -4,6 +4,34 @@

menu "Security options"

+config SNS_SIGNED
+ bool "Enable sns-signed binaries (EXPERIMENTAL)"
+ depends on (EXT2_FS_XATTR || EXT3_FS_XATTR || EXT4DEV_FS_XATTR || REISERFS_FS_XATTR || JFFS2_FS_XATTR || CIFS_XATTR) && (CRYPTO_SHA1 || CRYPTO_HMAC || CRYPTO_MD5) && MMU && EXPERIMENTAL
+ help
+ This option turns on sns-signatures of binaries. Requires extended
+ attributes and cryptographic hashes/HMAC support. HMAC is preferred.
+
+ This will leave your system unusable without proper preparation of
+ your sbit-files.
+
+ If you don't know exactly what you are doing, answer N.
+
+config SNS_SIGNED_SETUID
+ bool "Enables sns-signed binaries mandatory for suid-bits"
+ depends on SNS_SIGNED
+ help
+ Mandates signed binaries for suidbits.
+
+ If you don't know exactly what you are doing, answer N.
+
+config SNS_SIGNED_SETGID
+ bool "Enables sns-signed binaries mandatory for sgid-bits"
+ depends on SNS_SIGNED
+ help
+ Mandates signed binaries for sgidbits.
+
+ If you don't know exactly what you are doing, answer N.
+
config KEYS
bool "Enable access key retention support"
help
diff --git a/security/Makefile b/security/Makefile
index ef87df2..677b978 100644
--- a/security/Makefile
+++ b/security/Makefile
@@ -16,3 +16,4 @@ 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_SNS_SIGNED) += sns.o
diff --git a/security/sns.c b/security/sns.c
new file mode 100644
index 0000000..4403e5a
--- /dev/null
+++ b/security/sns.c
@@ -0,0 +1,104 @@
+#include <linux/crypto.h>
+#include <linux/bug.h>
+#include <linux/err.h>
+#include <linux/scatterlist.h>
+#include <linux/xattr.h>
+#include <linux/string.h>
+#include <linux/sched.h>
+#include <linux/sns.h>
+
+#include "sns_secret_key.dat"
+
+#define SNS_MAX_DIGEST_SIZE 64
+
+struct sns_attr {
+ char algname[CRYPTO_MAX_ALG_NAME+1];
+ char hash_value[SNS_MAX_DIGEST_SIZE];
+};
+
+
+static int sns_sig_reader(read_descriptor_t *desc, struct page *page, unsigned long offset, unsigned long nr)
+{
+ struct scatterlist s;
+ struct hash_desc *hash_desc = (struct hash_desc *) desc->arg.data;
+ unsigned int read;
+
+ s.page = page;
+ s.offset = offset;
+ s.length = nr;
+ read = nr - offset;
+ crypto_hash_update(hash_desc, &s, read);
+ desc->written += read;
+ desc->count -= read;
+ return read;
+}
+
+/*
+ * check file signature for setuid
+ */
+
+int sns_signature_valid(struct file *file)
+{
+ unsigned long i;
+ struct inode *inode = file->f_mapping->host;
+ struct crypto_hash *tfm;
+ struct hash_desc hash_desc;
+ struct sns_attr attrdata;
+ char hash_result[SNS_MAX_DIGEST_SIZE];
+ struct xattr_handler *handler;
+ const char *namespaces[2] = { "trusted.", NULL };
+ int ret = 0;
+ loff_t pos = 0;
+ read_descriptor_t read_desc;
+
+ handler = xattr_resolve_name_sns(inode->i_sb->s_xattr, namespaces);
+ if (unlikely(!handler)) {
+ printk(KERN_DEBUG "sns_signature_valid: xattr_resolve_name failed\n");
+ return 0;
+ }
+ memset(&attrdata, 0, sizeof(struct sns_attr));
+ i = handler->get(inode, "sns", &attrdata, sizeof(struct sns_attr));
+ if (i != sizeof(struct sns_attr)) {
+ printk(KERN_DEBUG "sns_signature_valid: invalid xattr found\n");
+ return 0;
+ }
+ attrdata.algname[CRYPTO_MAX_ALG_NAME] = '\0';
+ read_desc.count = i_size_read(inode);
+ if (unlikely(!read_desc.count)) {
+ printk(KERN_DEBUG "sns_signature_valid: inode of file has invalid size\n");
+ return 0;
+ }
+ tfm = crypto_alloc_hash(attrdata.algname, 0, CRYPTO_ALG_ASYNC);
+ if (unlikely(IS_ERR(tfm))) {
+ printk("sns_signature_valid: %s unavailable\n", attrdata.algname);
+ return 0;
+ /*FIXME: failure mode should be defined at build-time */
+ }
+ memset(hash_result, 0, SNS_MAX_DIGEST_SIZE); /*Needed?*/
+ hash_desc.tfm = tfm;
+ hash_desc.flags = 0;
+ read_desc.arg.data = &hash_desc;
+ read_desc.written = 0;
+ if (crypto_hash_setkey(tfm, sns_secret_key, SNS_SECRET_KEY_SIZE)) {
+ printk("sns_signature_valid: hash function did not accept setkey\n");
+ return 0;
+ }
+ crypto_hash_init(&hash_desc);
+ do_generic_file_read(file, &pos, &read_desc, sns_sig_reader);
+ crypto_hash_final(&hash_desc, hash_result);
+ BUG_ON(read_desc.written != i_size_read(inode));
+#ifdef SNS_SIGNED_DEBUG
+ printk("sns_signature_valid: attrdata.algname = %s\n", attrdata.algname);
+ printk("sns_signature_valid: attrib: ");
+ for (i = 0; i < SNS_MAX_DIGEST_SIZE; i++)
+ printk("%02x ", (unsigned char) attrdata.hash_value[i]);
+ printk("\n");
+ printk("sns_signature_valid: result: ");
+ for (i = 0; i < SNS_MAX_DIGEST_SIZE; i++)
+ printk("%02x ", (unsigned char) hash_result[i]);
+ printk("\n");
+#endif
+ ret = !memcmp(attrdata.hash_value, hash_result, SNS_MAX_DIGEST_SIZE);
+ crypto_free_hash(tfm);
+ return ret;
+}
diff --git a/security/sns_secret_key.dat b/security/sns_secret_key.dat
new file mode 100644
index 0000000..aec09da
--- /dev/null
+++ b/security/sns_secret_key.dat
@@ -0,0 +1,5 @@
+#define SNS_SECRET_KEY_SIZE 8
+static char sns_secret_key[SNS_SECRET_KEY_SIZE] =
+ {
+ 'd', 'e', 'a', 'd', 'b', 'e', 'e', 'f'
+ };
--
1.5.2.1

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/