Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date: Sat, 13 Jan 2018 23:34:39 +0200
From: Dan Aloni <dan@...nelim.com>
To: linux-kernel@...r.kernel.org,
	kernel-hardening@...ts.openwall.com
Cc: Petr Mladek <pmladek@...e.com>,
	Sergey Senozhatsky <sergey.senozhatsky@...il.com>,
	Linus Torvalds <torvalds@...ux-foundation.org>
Subject: [PATCHv2 5/7] printk: allow kmsg to be encrypted using public key encryption

This commit enables the kernel to encrypt the free-form text that
is generated by printk() before it is brought up to `dmesg` in
userspace.

The encryption is made using one of the trusted public keys which
are kept built-in inside the kernel. These keys are presently
also used for verifying kernel modules and userspace-supplied
firmwares.

CC: Petr Mladek <pmladek@...e.com>
CC: Sergey Senozhatsky <sergey.senozhatsky@...il.com>
CC: Linus Torvalds <torvalds@...ux-foundation.org>
Signed-off-by: Dan Aloni <dan@...nelim.com>
---
 Documentation/ioctl/ioctl-number.txt |   1 +
 include/uapi/linux/kmsg.h            |  18 ++
 init/Kconfig                         |  11 +
 kernel/printk/printk.c               | 450 +++++++++++++++++++++++++++++++++++
 4 files changed, 480 insertions(+)
 create mode 100644 include/uapi/linux/kmsg.h

diff --git a/Documentation/ioctl/ioctl-number.txt b/Documentation/ioctl/ioctl-number.txt
index 3e3fdae5f3ed..eafa24cddf3f 100644
--- a/Documentation/ioctl/ioctl-number.txt
+++ b/Documentation/ioctl/ioctl-number.txt
@@ -226,6 +226,7 @@ Code  Seq#(hex)	Include File		Comments
 'f'	00-0F	fs/ocfs2/ocfs2_fs.h	conflict!
 'g'	00-0F	linux/usb/gadgetfs.h
 'g'	20-2F	linux/usb/g_printer.h
+'g'	30-3F	uapi/linux/kmsg.h
 'h'	00-7F				conflict! Charon filesystem
 					<mailto:zapman@...erlan.net>
 'h'	00-1F	linux/hpet.h		conflict!
diff --git a/include/uapi/linux/kmsg.h b/include/uapi/linux/kmsg.h
new file mode 100644
index 000000000000..497040740d69
--- /dev/null
+++ b/include/uapi/linux/kmsg.h
@@ -0,0 +1,18 @@
+#ifndef _LINUX_UAPI_KMSG_H
+#define _LINUX_UAPI_KMSG_H
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+struct kmsg_ioctl_get_encrypted_key {
+	void __user *output_buffer;
+	__u64 buffer_size;
+	__u64 key_size;
+};
+
+#define KMSG_IOCTL_BASE 'g'
+
+#define KMSG_IOCTL__GET_ENCRYPTED_KEY  _IOWR(KMSG_IOCTL_BASE, 0x30, \
+	    struct kmsg_ioctl_get_encrypted_key)
+
+#endif /* _LINUX_DN_H */
diff --git a/init/Kconfig b/init/Kconfig
index a9a2e2c86671..8e07a8f9e5c6 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -1769,6 +1769,17 @@ config MODULE_SIG
 	  debuginfo strip done by some packagers (such as rpmbuild) and
 	  inclusion into an initramfs that wants the module size reduced.
 
+config KMSG_ENCRYPTION
+	bool "Encrypt /dev/kmsg (viewing dmesg will require decryption!)"
+	depends on SYSTEM_TRUSTED_KEYRING
+	select BASE64_ARMOR
+	help
+	  This enables strong encryption of messages generated by the kernel,
+	  to defend against most kinds of information leaks.
+
+	  Note that this option adds the OpenSSL development packages as a
+	  kernel build dependency so that certificates can be generated.
+
 config MODULE_SIG_FORCE
 	bool "Require modules to be validly signed"
 	depends on MODULE_SIG
diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c
index b9006617710f..898094fb87bd 100644
--- a/kernel/printk/printk.c
+++ b/kernel/printk/printk.c
@@ -48,6 +48,14 @@
 #include <linux/sched/clock.h>
 #include <linux/sched/debug.h>
 #include <linux/sched/task_stack.h>
+#include <linux/scatterlist.h>
+#include <linux/random.h>
+#include <linux/base64-armor.h>
+#include <crypto/akcipher.h>
+#include <crypto/public_key.h>
+#include <crypto/aead.h>
+#include <uapi/linux/kmsg.h>
+#include <keys/system_keyring.h>
 
 #include <linux/uaccess.h>
 #include <asm/sections.h>
@@ -100,6 +108,10 @@ enum devkmsg_log_masks {
 	DEVKMSG_LOG_MASK_LOCK           = BIT(__DEVKMSG_LOG_BIT_LOCK),
 };
 
+#define CRYPT_KMSG_KEY_LEN         16
+#define CRYPT_KMSG_AUTH_LEN        16
+#define CRYPT_KMSG_TEXT_META_MAX   32
+
 /* Keep both the 'on' and 'off' bits clear, i.e. ratelimit by default: */
 #define DEVKMSG_LOG_MASK_DEFAULT	0
 
@@ -744,12 +756,33 @@ static ssize_t msg_print_ext_body(char *buf, size_t size,
 	return p - buf;
 }
 
+#ifdef CONFIG_KMSG_ENCRYPTION
+static int __ro_after_init kmsg_encrypt = 1;
+static int __init control_kmsg_encrypt(char *str)
+{
+	get_option(&str, &kmsg_encrypt);
+	return 0;
+}
+__setup("kmsg_encrypt=", control_kmsg_encrypt);
+
+struct devkmsg_crypt {
+	u8 key[CRYPT_KMSG_KEY_LEN];
+	u8 *encrypted_key;
+	size_t encrypted_key_len;
+	bool encrypted_key_read;
+	struct crypto_aead *sk_tfm;
+};
+#else
+struct devkmsg_crypt {};
+#endif
+
 /* /dev/kmsg - userspace message inject/listen interface */
 struct devkmsg_user {
 	u64 seq;
 	u32 idx;
 	struct ratelimit_state rs;
 	struct mutex lock;
+	struct devkmsg_crypt crypt;
 	char buf[CONSOLE_EXT_LOG_MAX];
 };
 
@@ -816,6 +849,358 @@ static ssize_t devkmsg_write(struct kiocb *iocb, struct iov_iter *from)
 	return ret;
 }
 
+#ifdef CONFIG_KMSG_ENCRYPTION
+
+static int devkmsg_encrypt_key(struct devkmsg_crypt *crypt,
+			       struct crypto_akcipher *ak_tfm)
+{
+	const struct public_key *pkey;
+	struct akcipher_request *req;
+	unsigned int out_len_max;
+	struct scatterlist src, dst;
+	void *outbuf_enc = NULL;
+	struct crypto_wait wait;
+	struct key *key;
+	int err;
+
+	if (!kmsg_encrypt)
+		return 0;
+
+	key = find_trusted_asymmetric_key(NULL, NULL);
+	if (IS_ERR(key))
+		return PTR_ERR(key);
+
+	pkey = key->payload.data[asym_crypto];
+	BUG_ON(!pkey);
+
+	err = -ENOMEM;
+	req = akcipher_request_alloc(ak_tfm, GFP_KERNEL);
+	if (!req)
+		goto exit2;
+
+	err = crypto_akcipher_set_pub_key(ak_tfm,
+					  pkey->key, pkey->keylen);
+	if (err)
+		goto exit;
+
+	err = -ENOMEM;
+	out_len_max = crypto_akcipher_maxsize(ak_tfm);
+	outbuf_enc = kzalloc(out_len_max, GFP_KERNEL);
+	if (!outbuf_enc)
+		goto exit;
+
+	crypto_init_wait(&wait);
+	sg_init_one(&src, crypt->key, sizeof(crypt->key));
+	sg_init_one(&dst, outbuf_enc, out_len_max);
+	akcipher_request_set_crypt(req, &src, &dst, sizeof(crypt->key),
+				   out_len_max);
+	akcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
+				      crypto_req_done, &wait);
+
+	err = crypto_wait_req(crypto_akcipher_encrypt(req), &wait);
+	if (err) {
+		kfree(outbuf_enc);
+		goto exit;
+	}
+
+	crypt->encrypted_key_len = req->dst_len;
+	crypt->encrypted_key = outbuf_enc;
+
+exit:
+	akcipher_request_free(req);
+exit2:
+	key_put(key);
+	return err;
+}
+
+static int devkmsg_crypt_init(struct devkmsg_crypt *crypt)
+{
+	struct crypto_akcipher *ak_tfm;
+	struct crypto_aead *sk_tfm;
+	int err;
+
+	if (!kmsg_encrypt)
+		return 0;
+
+	crypt->encrypted_key = NULL;
+	crypt->encrypted_key_len = 0;
+	crypt->encrypted_key_read = false;
+
+	sk_tfm = crypto_alloc_aead("gcm(aes)", 0, 0);
+	if (IS_ERR(sk_tfm))
+		return PTR_ERR(sk_tfm);
+
+	get_random_bytes(crypt->key, sizeof(crypt->key));
+
+	err = crypto_aead_setkey(sk_tfm, crypt->key, sizeof(crypt->key));
+	if (err < 0)
+		goto fail;
+
+	err = crypto_aead_setauthsize(sk_tfm, CRYPT_KMSG_AUTH_LEN);
+	if (err < 0)
+		goto fail;
+
+	ak_tfm = crypto_alloc_akcipher("pkcs1pad(rsa,sha256)", 0, 0);
+	if (IS_ERR(ak_tfm)) {
+		err = PTR_ERR(ak_tfm);
+		goto fail;
+	}
+
+	err = devkmsg_encrypt_key(crypt, ak_tfm);
+	crypto_free_akcipher(ak_tfm);
+
+	if (err < 0)
+		goto fail;
+
+	crypt->sk_tfm = sk_tfm;
+	return 0;
+
+fail:
+	crypto_free_aead(sk_tfm);
+	return err;
+}
+
+static void devkmsg_crypt_free(struct devkmsg_crypt *crypt)
+{
+	if (!kmsg_encrypt)
+		return;
+
+	crypto_free_aead(crypt->sk_tfm);
+	kfree(crypt->encrypted_key);
+}
+
+static int devkmsg_encrypt_inplace(struct devkmsg_user *user,
+				   size_t hdr_len, size_t len,
+				   size_t *out_len)
+{
+	DECLARE_CRYPTO_WAIT(wait);
+	const char *newline_pos;
+	const char *prefix = "M:";
+	char suffix[CRYPT_KMSG_TEXT_META_MAX], *ciphertext_start;
+	int all_cryptmsg_encoded_len;
+	int ciphertext_len;
+	int ciphertext_with_auth;
+	int ciphertext_with_auth_iv;
+	int armored_ciphertext_with_auth_iv;
+	int dict_len;
+	int prefix_len = strlen(prefix);
+	int suffix_size;
+	int i;
+	size_t len_no_newline;
+	ssize_t ret;
+	struct aead_request *aead_req;
+	struct scatterlist sgio_in;
+	struct scatterlist sgio_out;
+	u8 *iv;
+	unsigned int iv_len;
+
+	if (!kmsg_encrypt)
+		return 0;
+
+	newline_pos = strnchr(user->buf, len, '\n');
+	if (!newline_pos)
+		return -EINVAL;
+
+	/* We do not encrypt the dict, but only the free-form text. */
+	len_no_newline = newline_pos - user->buf;
+
+	/* If dict_len == 1 it's an empty dict, only a '\n' */
+	dict_len = len - len_no_newline;
+
+	aead_req = aead_request_alloc(user->crypt.sk_tfm, GFP_KERNEL);
+	if (!aead_req)
+		return -ENOMEM;
+
+	iv_len = crypto_aead_ivsize(user->crypt.sk_tfm);
+
+	ciphertext_len = len_no_newline - hdr_len;
+	ciphertext_with_auth = ciphertext_len + CRYPT_KMSG_AUTH_LEN;
+	ciphertext_with_auth_iv = ciphertext_with_auth + iv_len;
+
+	armored_ciphertext_with_auth_iv =
+	    base64_encode_buffer_bound(ciphertext_with_auth_iv);
+
+	all_cryptmsg_encoded_len = hdr_len + prefix_len +
+	    armored_ciphertext_with_auth_iv;
+
+	suffix_size =
+		scnprintf(suffix, sizeof(suffix), ",%u,%u",
+			  CRYPT_KMSG_AUTH_LEN, iv_len);
+
+	/*
+	 * Check that we are not overflowing with the rearrangement
+	 * of the encrypted message.
+	 */
+	ret = -EINVAL;
+	if (all_cryptmsg_encoded_len + suffix_size + dict_len +
+	    CRYPT_KMSG_TEXT_META_MAX + ciphertext_with_auth
+	    > sizeof(user->buf))
+		goto out;
+
+	/* Move away the dict farther down so we don't overwrite it */
+	if (dict_len > 0)
+		memmove(&user->buf[all_cryptmsg_encoded_len + suffix_size],
+			&user->buf[len_no_newline],
+			dict_len);
+
+	/*
+	 * We are using the end of user->buf as a staging area for the
+	 * ciphertext + auth + iv, before we do base64-encoding of it,
+	 * writing the encoded output to its original place, right
+	 * after the prefix.
+	 */
+
+	/* Initialize IV */
+	iv = &user->buf[sizeof(user->buf) - iv_len];
+
+	get_random_bytes(iv, iv_len);
+
+	/* Do the encryption */
+	sg_init_one(&sgio_in, user->buf + hdr_len, ciphertext_with_auth);
+	sg_init_one(&sgio_out, iv - ciphertext_with_auth, ciphertext_with_auth);
+	aead_request_set_crypt(aead_req, &sgio_in, &sgio_out,
+			       ciphertext_len, iv);
+	aead_request_set_ad(aead_req, 0);
+	aead_request_set_callback(aead_req, CRYPTO_TFM_REQ_MAY_BACKLOG,
+				  crypto_req_done, &wait);
+
+	ret = crypto_wait_req(crypto_aead_encrypt(aead_req), &wait);
+
+	if (ret)
+		goto out;
+
+	/* Base64-encode the ciphertext + auth code + IV */
+
+	BUG_ON(hdr_len <= 1);
+
+	ciphertext_start = &user->buf[hdr_len + prefix_len];
+	ret = base64_armor(ciphertext_start,
+			   armored_ciphertext_with_auth_iv,
+			   iv - ciphertext_with_auth,
+			   &user->buf[sizeof(user->buf)]);
+
+	BUG_ON(ret < 0); /* Should indicate a real bug buffer bounds */
+
+	/* Convert newlines to '~' */
+
+	for (i = 0; i < ret; i++)
+		if (ciphertext_start[i] == '\n')
+			ciphertext_start[i] = '~';
+
+	/* Add prefixes and suffixes */
+
+	memcpy(&user->buf[hdr_len], prefix, prefix_len);
+	memcpy(&user->buf[all_cryptmsg_encoded_len], suffix, suffix_size);
+
+	len = all_cryptmsg_encoded_len + suffix_size + dict_len;
+	BUG_ON(len > sizeof(user->buf));
+
+	*out_len = len;
+	ret = 0;
+
+out:
+	aead_request_free(aead_req);
+	return ret;
+}
+
+static ssize_t devkmsg_encrypt_onetime_piggyback_key(struct devkmsg_user *user,
+						     char __user *buf,
+						     size_t count)
+{
+	/*
+	 * Send down the encryted session key as the first message. We identify
+	 * it using the 'K:' prefix.
+	 */
+	const char *prefix = "7,0,0,-;K:";
+	size_t prefix_len;
+	size_t base64_len, i;
+	size_t len = 0;
+	char newline = '\n';
+
+	if (user->crypt.encrypted_key_len == 0 ||
+	    user->crypt.encrypted_key_read)
+		return 0;
+
+	prefix_len = strlen(prefix);
+
+	if (prefix_len > count)
+		return -EINVAL;
+
+	if (copy_to_user(buf, prefix, prefix_len))
+		return -EFAULT;
+
+	/* Hex-encode and copy to userspace */
+
+	len += prefix_len;
+
+	/* Base64-encode the ciphertext + auth code + IV */
+
+	base64_len = base64_armor(user->buf, sizeof(user->buf),
+		  user->crypt.encrypted_key,
+		  &user->crypt.encrypted_key[user->crypt.encrypted_key_len]);
+
+	BUG_ON(base64_len < 0); /* Should indicate a real bug buffer bounds */
+
+	/* Convert newlines to '~' */
+
+	for (i = 0; i < base64_len; i++)
+		if (user->buf[i] == '\n')
+			user->buf[i] = '~';
+
+	if (len + base64_len > count)
+		return -EINVAL;
+
+	if (copy_to_user(buf + len, user->buf, base64_len))
+		return -EFAULT;
+
+	len += base64_len;
+
+	if (len + 1 > count)
+		return -EINVAL;
+
+	if (copy_to_user(buf + len, &newline, 1))
+		return -EFAULT;
+
+	len += 1;
+
+	user->crypt.encrypted_key_read = true;
+	return len;
+}
+
+static bool devkmsg_crypt_allow_syslog(void)
+{
+	return kmsg_encrypt != 0;
+}
+
+#else
+
+static void devkmsg_crypt_free(struct devkmsg_crypt *crypt) {}
+static int devkmsg_crypt_init(struct devkmsg_crypt *crypt)
+{
+	return 0;
+}
+
+static int devkmsg_encrypt_inplace(struct devkmsg_user *user,
+				   size_t hdr_len, size_t len,
+				   size_t *out_len)
+{
+	return 0;
+}
+
+static ssize_t devkmsg_encrypt_onetime_piggyback_key(struct devkmsg_user *user,
+						     char __user *buf,
+						     size_t count)
+{
+	return 0;
+}
+
+static bool devkmsg_crypt_allow_syslog(void)
+{
+	return true;
+}
+
+#endif
+
 static ssize_t devkmsg_read(struct file *file, char __user *buf,
 			    size_t count, loff_t *ppos)
 {
@@ -823,6 +1208,7 @@ static ssize_t devkmsg_read(struct file *file, char __user *buf,
 	struct printk_log *msg;
 	size_t len;
 	ssize_t ret;
+	int hdr_len;
 
 	if (!user)
 		return -EBADF;
@@ -831,6 +1217,10 @@ static ssize_t devkmsg_read(struct file *file, char __user *buf,
 	if (ret)
 		return ret;
 
+	ret = devkmsg_encrypt_onetime_piggyback_key(user, buf, count);
+	if (ret != 0)
+		goto out;
+
 	logbuf_lock_irq();
 	while (user->seq == log_next_seq) {
 		if (file->f_flags & O_NONBLOCK) {
@@ -859,6 +1249,7 @@ static ssize_t devkmsg_read(struct file *file, char __user *buf,
 	msg = log_from_idx(user->idx);
 	len = msg_print_ext_header(user->buf, sizeof(user->buf),
 				   msg, user->seq);
+	hdr_len = len;
 	len += msg_print_ext_body(user->buf + len, sizeof(user->buf) - len,
 				  log_dict(msg), msg->dict_len,
 				  log_text(msg), msg->text_len);
@@ -867,6 +1258,10 @@ static ssize_t devkmsg_read(struct file *file, char __user *buf,
 	user->seq++;
 	logbuf_unlock_irq();
 
+	ret = devkmsg_encrypt_inplace(user, hdr_len, len, &len);
+	if (ret)
+		goto out;
+
 	if (len > count) {
 		ret = -EINVAL;
 		goto out;
@@ -876,6 +1271,7 @@ static ssize_t devkmsg_read(struct file *file, char __user *buf,
 		ret = -EFAULT;
 		goto out;
 	}
+
 	ret = len;
 out:
 	mutex_unlock(&user->lock);
@@ -943,6 +1339,43 @@ static unsigned int devkmsg_poll(struct file *file, poll_table *wait)
 	return ret;
 }
 
+static long devkmsg_ioctl(struct file *file, unsigned int ioctl,
+			  unsigned long arg)
+{
+	switch (ioctl) {
+#ifdef CONFIG_KMSG_ENCRYPTION
+	case KMSG_IOCTL__GET_ENCRYPTED_KEY: {
+		struct devkmsg_user *user = file->private_data;
+		struct kmsg_ioctl_get_encrypted_key params;
+		int err;
+
+		if (copy_from_user(&params, (void __user *)arg, sizeof(params)))
+			return -EFAULT;
+
+		if (!user->crypt.encrypted_key) {
+			err = -ENOENT;
+		} else {
+			params.key_size = user->crypt.encrypted_key_len;
+
+			if (user->crypt.encrypted_key_len > params.buffer_size)
+				err = -E2BIG;
+			else
+				err = copy_to_user(params.output_buffer,
+					     user->crypt.encrypted_key,
+					     user->crypt.encrypted_key_len);
+		}
+
+		if (copy_to_user((void __user *)arg, &params, sizeof(params)))
+			return -EFAULT;
+
+		return err;
+	}
+#endif
+	default:
+		return -EINVAL;
+	}
+}
+
 static int devkmsg_open(struct inode *inode, struct file *file)
 {
 	struct devkmsg_user *user;
@@ -963,6 +1396,12 @@ static int devkmsg_open(struct inode *inode, struct file *file)
 	if (!user)
 		return -ENOMEM;
 
+	err = devkmsg_crypt_init(&user->crypt);
+	if (err < 0) {
+		kfree(user);
+		return err;
+	}
+
 	ratelimit_default_init(&user->rs);
 	ratelimit_set_flags(&user->rs, RATELIMIT_MSG_ON_RELEASE);
 
@@ -987,6 +1426,7 @@ static int devkmsg_release(struct inode *inode, struct file *file)
 	ratelimit_state_exit(&user->rs);
 
 	mutex_destroy(&user->lock);
+	devkmsg_crypt_free(&user->crypt);
 	kfree(user);
 	return 0;
 }
@@ -997,6 +1437,7 @@ const struct file_operations kmsg_fops = {
 	.write_iter = devkmsg_write,
 	.llseek = devkmsg_llseek,
 	.poll = devkmsg_poll,
+	.unlocked_ioctl = devkmsg_ioctl,
 	.release = devkmsg_release,
 };
 
@@ -1442,6 +1883,8 @@ int do_syslog(int type, char __user *buf, int len, int source)
 	case SYSLOG_ACTION_OPEN:	/* Open log */
 		break;
 	case SYSLOG_ACTION_READ:	/* Read from log */
+		if (!devkmsg_crypt_allow_syslog())
+			return -EPERM;
 		if (!buf || len < 0)
 			return -EINVAL;
 		if (!len)
@@ -1460,6 +1903,8 @@ int do_syslog(int type, char __user *buf, int len, int source)
 		/* FALL THRU */
 	/* Read last kernel messages */
 	case SYSLOG_ACTION_READ_ALL:
+		if (!devkmsg_crypt_allow_syslog())
+			return -EPERM;
 		if (!buf || len < 0)
 			return -EINVAL;
 		if (!len)
@@ -1470,6 +1915,8 @@ int do_syslog(int type, char __user *buf, int len, int source)
 		break;
 	/* Clear ring buffer */
 	case SYSLOG_ACTION_CLEAR:
+		if (!devkmsg_crypt_allow_syslog())
+			return -EPERM;
 		syslog_print_all(NULL, 0, true);
 		break;
 	/* Disable logging to console */
@@ -1497,6 +1944,9 @@ int do_syslog(int type, char __user *buf, int len, int source)
 		break;
 	/* Number of chars in the log buffer */
 	case SYSLOG_ACTION_SIZE_UNREAD:
+		if (!devkmsg_crypt_allow_syslog())
+			return -EPERM;
+
 		logbuf_lock_irq();
 		if (syslog_seq < log_first_seq) {
 			/* messages are gone, move to first one */
-- 
2.14.3

Powered by blists - more mailing lists

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.