diff mbox

[3/5] kernel/printk: allow kmsg to be encrypted using public key encryption

Message ID 20171230175804.7354-4-alonid@gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

Dan Aloni Dec. 30, 2017, 5:58 p.m. UTC
From: Dan Aloni <dan@kernelim.com>

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.

Signed-off-by: Dan Aloni <dan@kernelim.com>
---
 include/uapi/linux/kmsg.h |  18 ++
 init/Kconfig              |  10 ++
 kernel/printk/printk.c    | 422 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 450 insertions(+)
 create mode 100644 include/uapi/linux/kmsg.h

Comments

Randy Dunlap Dec. 30, 2017, 8:39 p.m. UTC | #1
On 12/30/2017 09:58 AM, Dan Aloni wrote:
> From: Dan Aloni <dan@kernelim.com>
> 
> 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.
> 
> Signed-off-by: Dan Aloni <dan@kernelim.com>
> ---
>  include/uapi/linux/kmsg.h |  18 ++
>  init/Kconfig              |  10 ++
>  kernel/printk/printk.c    | 422 ++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 450 insertions(+)
>  create mode 100644 include/uapi/linux/kmsg.h
> 
> diff --git a/include/uapi/linux/kmsg.h b/include/uapi/linux/kmsg.h
> new file mode 100644
> index 000000000000..ae74f026d727
> --- /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 0x42
> +
> +#define KMSG_IOCTL__GET_ENCRYPTED_KEY  _IOWR(KMSG_IOCTL_BASE, 0xe1, \
> +	    struct kmsg_ioctl_get_encrypted_key)
> +

The ioctl info needs to be added to Documentation/ioctl/ioctl-number.txt.
BTW, 0x42 == 'B', with number 0xe1, conflicts with advanced bbus, whatever
that is.  But at least it should be added to the doc file.

> +#endif /* _LINUX_DN_H */

> diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c
> index b9006617710f..c50b9cb60b82 100644
> --- a/kernel/printk/printk.c
> +++ b/kernel/printk/printk.c

> @@ -744,12 +755,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);

See comment in the documentation patch:  please change the parameter name (no dot).
diff mbox

Patch

diff --git a/include/uapi/linux/kmsg.h b/include/uapi/linux/kmsg.h
new file mode 100644
index 000000000000..ae74f026d727
--- /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 0x42
+
+#define KMSG_IOCTL__GET_ENCRYPTED_KEY  _IOWR(KMSG_IOCTL_BASE, 0xe1, \
+	    struct kmsg_ioctl_get_encrypted_key)
+
+#endif /* _LINUX_DN_H */
diff --git a/init/Kconfig b/init/Kconfig
index 2934249fba46..3990fe24a9d3 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -1757,6 +1757,16 @@  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
+	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..c50b9cb60b82 100644
--- a/kernel/printk/printk.c
+++ b/kernel/printk/printk.c
@@ -48,6 +48,13 @@ 
 #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 <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 +107,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 +755,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 +848,330 @@  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 const char hex_legend[] = "0123456789abcdef";
+
+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];
+	int all_cryptmsg_encoded_len;
+	int ciphertext_with_auth;
+	int dict_len;
+	int i, j;
+	int prefix_len = strlen(prefix);
+	int suffix_size;
+	size_t len_no_newline;
+	ssize_t ret;
+	struct aead_request *aead_req;
+	struct scatterlist sgio;
+	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_with_auth = (len_no_newline - hdr_len
+				+ CRYPT_KMSG_AUTH_LEN + iv_len) * 2;
+	all_cryptmsg_encoded_len = hdr_len + prefix_len + ciphertext_with_auth;
+
+	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 > 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);
+
+	/* Initialize IV */
+	iv = &user->buf[len_no_newline + CRYPT_KMSG_AUTH_LEN];
+
+	get_random_bytes(iv, iv_len);
+
+	/* Do the encryption */
+
+	sg_init_one(&sgio, user->buf + hdr_len,
+		    len_no_newline + CRYPT_KMSG_AUTH_LEN - hdr_len);
+
+	aead_request_set_crypt(aead_req, &sgio, &sgio,
+			       len_no_newline - hdr_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;
+
+	/* Hex-encode the ciphertext + auth code + IV */
+
+	BUG_ON(hdr_len <= 1);
+
+	for (j = len_no_newline - 1 + CRYPT_KMSG_AUTH_LEN + iv_len,
+	     i = all_cryptmsg_encoded_len - 2;
+	     i >= hdr_len + prefix_len; j--, i -= 2)
+	{
+		u8 octet = user->buf[j];
+		user->buf[i + 0] = hex_legend[(octet >> 4) & 0xf];
+		user->buf[i + 1] = hex_legend[(octet >> 0) & 0xf];
+	}
+
+	/* 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)
+{
+	/*
+	 * 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 key_len_remaining;
+	size_t len = 0;
+	char buffer[0x100];
+	char newline = '\n';
+	u8 *key_part;
+
+	if (user->crypt.encrypted_key_len == 0 || user->crypt.encrypted_key_read)
+		return 0;
+
+	prefix_len = strlen(prefix);
+
+	if (copy_to_user(buf + len, prefix, prefix_len))
+		return -EFAULT;
+
+	/* Hex-encode and copy to userspace */
+
+	len += prefix_len;
+
+	key_len_remaining = user->crypt.encrypted_key_len;
+	key_part = user->crypt.encrypted_key;
+
+	while (key_len_remaining > 0) {
+		int p = min(key_len_remaining, sizeof(buffer)/2), i, j;
+
+		for (j = 0, i = 0; i < p; j += 2, i++) {
+			u8 octet = key_part[i];
+
+			buffer[j + 0] = hex_legend[(octet >> 4) & 0xf];
+			buffer[j + 1] = hex_legend[(octet >> 0) & 0xf];
+		}
+
+		if (copy_to_user(buf + len, buffer, p * 2))
+			return -EFAULT;
+
+		key_len_remaining -= p;
+		key_part += p;
+		len += p * 2;
+	}
+
+	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)
+{
+	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 +1179,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 +1188,10 @@  static ssize_t devkmsg_read(struct file *file, char __user *buf,
 	if (ret)
 		return ret;
 
+	ret = devkmsg_encrypt_onetime_piggyback_key(user, buf);
+	if (ret != 0)
+		goto out;
+
 	logbuf_lock_irq();
 	while (user->seq == log_next_seq) {
 		if (file->f_flags & O_NONBLOCK) {
@@ -859,6 +1220,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);
@@ -872,10 +1234,15 @@  static ssize_t devkmsg_read(struct file *file, char __user *buf,
 		goto out;
 	}
 
+	ret = devkmsg_encrypt_inplace(user, hdr_len, len, &len);
+	if (ret)
+		goto out;
+
 	if (copy_to_user(buf, user->buf, len)) {
 		ret = -EFAULT;
 		goto out;
 	}
+
 	ret = len;
 out:
 	mutex_unlock(&user->lock);
@@ -943,6 +1310,44 @@  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 +1368,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 +1398,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 +1409,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 +1855,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 +1875,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 +1887,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 +1916,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 */