2018-01-13 21:36:17

by Dan Aloni

[permalink] [raw]
Subject: [PATCHv2 0/7] RFC: Public key encryption of dmesg by the kernel

Changes from v1 [1]:

- Made suggested fixes following a review from Randy Dunlap
- Modified the ASCII encoding of cipher text to base64 instead of hex,
with newlines replaced by '~' ; updated dmesg-decipher for it too
- Moved base64 code from fs/ceph to lib, and improved it a bit
- Improved checks that we are not overflowing the user buffer when
using copy_to_user to in the added code
- Added some prints when errors in dmesg-decipher
- Fixes to Makefile at tools/ for building 'kmsg' (should it
build by default in target 'all'? There is an openssl depdendency.)
- checkpatch.pl linting

[1] https://lwn.net/Articles/742412/

Dan Aloni (7):
crypto: fix memory leak in rsa-kcs1pad encryption
Move net/ceph/armor to lib/ and add docs
base64-armor: add bounds checking
certs: allow in-kernel access of trusted keys
printk: allow kmsg to be encrypted using public key encryption
tools: add dmesg decryption program
docs: add dmesg encryption doc

Documentation/admin-guide/dmesg-encryption.rst | 116 +++++++
Documentation/admin-guide/index.rst | 1 +
Documentation/ioctl/ioctl-number.txt | 1 +
certs/system_keyring.c | 56 ++-
crypto/rsa-pkcs1pad.c | 9 -
include/keys/system_keyring.h | 3 +
include/linux/base64-armor.h | 70 ++++
include/uapi/linux/kmsg.h | 18 +
init/Kconfig | 11 +
kernel/printk/printk.c | 451 +++++++++++++++++++++++++
lib/Kconfig | 7 +
lib/Makefile | 1 +
net/ceph/armor.c => lib/base64-armor.c | 29 +-
net/ceph/Kconfig | 1 +
net/ceph/Makefile | 2 +-
net/ceph/crypto.c | 3 +-
net/ceph/crypto.h | 4 -
tools/Makefile | 9 +-
tools/kmsg/.gitignore | 1 +
tools/kmsg/Makefile | 14 +
tools/kmsg/dmesg-decipher.c | 354 +++++++++++++++++++
21 files changed, 1139 insertions(+), 22 deletions(-)
create mode 100644 Documentation/admin-guide/dmesg-encryption.rst
create mode 100644 include/linux/base64-armor.h
create mode 100644 include/uapi/linux/kmsg.h
rename net/ceph/armor.c => lib/base64-armor.c (75%)
create mode 100644 tools/kmsg/.gitignore
create mode 100644 tools/kmsg/Makefile
create mode 100644 tools/kmsg/dmesg-decipher.c

--
2.14.3


2018-01-13 21:36:20

by Dan Aloni

[permalink] [raw]
Subject: [PATCHv2 1/7] crypto: fix memory leak in rsa-kcs1pad encryption

The encryption mode of pkcs1pad never uses out_sg and out_buf, so
there's no need to allocate the buffer, which presently is not even
being freed.

CC: Herbert Xu <[email protected]>
Signed-off-by: Dan Aloni <[email protected]>
---
crypto/rsa-pkcs1pad.c | 9 ---------
1 file changed, 9 deletions(-)

diff --git a/crypto/rsa-pkcs1pad.c b/crypto/rsa-pkcs1pad.c
index 2908f93c3e55..e8354084ef4e 100644
--- a/crypto/rsa-pkcs1pad.c
+++ b/crypto/rsa-pkcs1pad.c
@@ -261,15 +261,6 @@ static int pkcs1pad_encrypt(struct akcipher_request *req)
pkcs1pad_sg_set_buf(req_ctx->in_sg, req_ctx->in_buf,
ctx->key_size - 1 - req->src_len, req->src);

- req_ctx->out_buf = kmalloc(ctx->key_size, GFP_KERNEL);
- if (!req_ctx->out_buf) {
- kfree(req_ctx->in_buf);
- return -ENOMEM;
- }
-
- pkcs1pad_sg_set_buf(req_ctx->out_sg, req_ctx->out_buf,
- ctx->key_size, NULL);
-
akcipher_request_set_tfm(&req_ctx->child_req, ctx->child);
akcipher_request_set_callback(&req_ctx->child_req, req->base.flags,
pkcs1pad_encrypt_sign_complete_cb, req);
--
2.14.3

2018-01-13 21:36:27

by Dan Aloni

[permalink] [raw]
Subject: [PATCHv2 4/7] certs: allow in-kernel access of trusted keys

CC: David Howells <[email protected]>
Signed-off-by: Dan Aloni <[email protected]>
---
certs/system_keyring.c | 56 ++++++++++++++++++++++++++++++++++++++++++-
include/keys/system_keyring.h | 3 +++
2 files changed, 58 insertions(+), 1 deletion(-)

diff --git a/certs/system_keyring.c b/certs/system_keyring.c
index 6251d1b27f0c..843a38b43fb1 100644
--- a/certs/system_keyring.c
+++ b/certs/system_keyring.c
@@ -131,6 +131,8 @@ static __init int system_trusted_keyring_init(void)
*/
device_initcall(system_trusted_keyring_init);

+static char *first_asymmetric_key_description;
+
/*
* Load the compiled-in list of X.509 certificates.
*/
@@ -172,8 +174,11 @@ static __init int load_system_certificate_list(void)
pr_err("Problem loading in-kernel X.509 certificate (%ld)\n",
PTR_ERR(key));
} else {
+ first_asymmetric_key_description =
+ kstrdup(key_ref_to_ptr(key)->description,
+ GFP_KERNEL);
pr_notice("Loaded X.509 cert '%s'\n",
- key_ref_to_ptr(key)->description);
+ first_asymmetric_key_description);
key_ref_put(key);
}
p += plen;
@@ -265,3 +270,52 @@ int verify_pkcs7_signature(const void *data, size_t len,
EXPORT_SYMBOL_GPL(verify_pkcs7_signature);

#endif /* CONFIG_SYSTEM_DATA_VERIFICATION */
+
+/**
+ * get_first_asymmetric_key - Find a key by ID.
+ * @keyring: The keys to search.
+ *
+ * Return the first asymmetric key in a keyring.
+ */
+static struct key *get_first_asymmetric_key(struct key *keyring)
+{
+ key_ref_t ref;
+
+ ref = keyring_search(make_key_ref(keyring, 1),
+ &key_type_asymmetric,
+ first_asymmetric_key_description);
+ if (IS_ERR(ref)) {
+ switch (PTR_ERR(ref)) {
+ case -EACCES:
+ case -ENOTDIR:
+ case -EAGAIN:
+ return ERR_PTR(-ENOKEY);
+ default:
+ return ERR_CAST(ref);
+ }
+ }
+
+ return key_ref_to_ptr(ref);
+}
+
+/**
+ * find_trusted_asymmetric_key - Find a key by ID in the builtin trusted
+ * keys keyring, or return the first key in that keyring.
+ *
+ * @id_0: The first ID to look for or NULL.
+ * @id_1: The second ID to look for or NULL.
+ *
+ * The preferred identifier is the id_0 and the fallback identifier is
+ * the id_1. If both are given, the lookup is by the former, but the
+ * latter must also match. If none are given, the first key is returned.
+ */
+struct key *find_trusted_asymmetric_key(const struct asymmetric_key_id *id_0,
+ const struct asymmetric_key_id *id_1)
+{
+ struct key *keyring = builtin_trusted_keys;
+
+ if (!id_0 && !id_1)
+ return get_first_asymmetric_key(keyring);
+
+ return find_asymmetric_key(keyring, id_0, id_1, false);
+}
diff --git a/include/keys/system_keyring.h b/include/keys/system_keyring.h
index 359c2f936004..0bef29eb8297 100644
--- a/include/keys/system_keyring.h
+++ b/include/keys/system_keyring.h
@@ -13,6 +13,7 @@
#define _KEYS_SYSTEM_KEYRING_H

#include <linux/key.h>
+#include <keys/asymmetric-type.h>

#ifdef CONFIG_SYSTEM_TRUSTED_KEYRING

@@ -61,5 +62,7 @@ static inline struct key *get_ima_blacklist_keyring(void)
}
#endif /* CONFIG_IMA_BLACKLIST_KEYRING */

+struct key *find_trusted_asymmetric_key(const struct asymmetric_key_id *id_0,
+ const struct asymmetric_key_id *id_1);

#endif /* _KEYS_SYSTEM_KEYRING_H */
--
2.14.3

2018-01-13 21:36:30

by Dan Aloni

[permalink] [raw]
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 <[email protected]>
CC: Sergey Senozhatsky <[email protected]>
CC: Linus Torvalds <[email protected]>
Signed-off-by: Dan Aloni <[email protected]>
---
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:[email protected]>
'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

2018-01-13 21:36:49

by Dan Aloni

[permalink] [raw]
Subject: [PATCHv2 7/7] docs: add dmesg encryption doc

Reviewed-by: Randy Dunlap <[email protected]>
Signed-off-by: Dan Aloni <[email protected]>
---
Documentation/admin-guide/dmesg-encryption.rst | 118 +++++++++++++++++++++++++
Documentation/admin-guide/index.rst | 1 +
2 files changed, 119 insertions(+)
create mode 100644 Documentation/admin-guide/dmesg-encryption.rst

diff --git a/Documentation/admin-guide/dmesg-encryption.rst b/Documentation/admin-guide/dmesg-encryption.rst
new file mode 100644
index 000000000000..5aedb8db3a7c
--- /dev/null
+++ b/Documentation/admin-guide/dmesg-encryption.rst
@@ -0,0 +1,118 @@
+Kernel message encryption
+-------------------------
+
+.. CONTENTS
+..
+.. - Overview
+.. - Reason for encrypting dmesg
+.. - Compile time and run time switches
+.. - Limitations
+.. - Decrypting dmesg
+
+
+========
+Overview
+========
+
+Similar to the module signing facility, it is also possible to have the kernel
+perform public key encryption of the kernel messages that are being generated
+by printk calls.
+
+The encryption can be performed for one of the trusted public keys in the
+kernel keyring, and by default will be performed against the kernel's module
+signing key.
+
+To prevent a run-time dependency inside printk itself, the encryption takes
+place upon trying to read ``/dev/kmsg`` which is the mechanism currently used
+by ``systemd`` to read kernel messages, and is also used by ``dmesg``
+invocations.
+
+The first line being read by a ``dmesg`` opener will be an artificial line
+containing an encrypted symmetric encryption session key, in RSA PKCS#1 format.
+The other lines are messages encrypted under an AES-128-GCM scheme. All binary
+ciphertext is base64-encoded, so that the ciphertext solely comprises of
+printable characters.
+
+===========
+Limitations
+===========
+
+There are various limitations one need to consider when enabling dmesg
+encryption:
+
+ * The metadata of kernel messages is not part of the encryption (timestamp,
+ log facility, log severity).
+
+ * The seldom accompanying dictionary is also not part of the encryption.
+
+ * Any output to any system console, happening when printk() itself is
+ executing, is also not encrypted. A potential attacker can load up
+ ``netconsole`` and have kernel messages being sent as plaintext to other
+ machines. Hopefully, on embedded devices, all system consoles are under
+ strict control of the developers.
+
+ * The syslog system call is barred from reading kmsg. Its present users are
+ few, as the system call's interface is mostly a fallback to an inaccessible
+ ``/dev/kmsg``. This is only an implementation limitation and that may be
+ addressed.
+
+ * kmsg buffers will still be saved as plaintext inside kdumps. The assumption
+ is that having an access to read a kdump is equivalent to full kernel
+ access anyway.
+
+===========================
+Reason for encryption dmesg
+===========================
+
+For years, dmesg has contained data which could be utilized by vulnerability
+exploiters, allowing for privilege escalations. Developers may leave key data
+such as pointers, indication of driver bugs, and more.
+
+The feature is mostly aimed for device manufacturers who are not keen on
+revealing the full details of kernel execution, bugs, and crashes to their
+users, but only to their developers, so that local programs running on the
+devices cannot use the data for 'rooting' and executing exploits.
+
+==================================
+Compile time and run time switches
+==================================
+
+In build time, this feature is controlled via the ``CONFIG_KMSG_ENCRYPTION``
+configuration variable.
+
+In run time, it can be turned off by providing `kmsg_encrypt=0` as a boot time
+parameter.
+
+================
+Decrypting dmesg
+================
+
+A supplied program in the kernel tree named ``dmesg-decipher`` uses the OpenSSL
+library along with the paired private key of the encryption in order to
+decipher an encrypted dmesg.
+
+An innocuous dmesg invocation will appear as such (with the ciphertexts
+shortened here for the brevity of this document)::
+
+ [ 0.000000] K:Zzgt0ovlRvwH....fQgbQ2tdjOzgYFwrzHU00XO4=
+ [ 0.000000] M:ogoKk3kCb6q5....1z8BVLr903/w==,16,12
+ [ 0.000000] M:CcxUnMRIHrjD....o+c1Zes=,16,12
+ ....
+
+The artificial ``K:`` message is generated per opening of ``/dev/kmsg``. It
+contains the encrypted session key. The encrypted dmesg lines follows it
+(prefix ``M:``).
+
+Provided with the private key, deciphering a dmesg output should be a
+straightforward process.
+
+For example, one can save an encrypted dmesg to ``dmesg.enc`` in one machine,
+then transfer it to another machine which contains access to the PEM with the
+decrypting private key, and use the the following command::
+
+ cat dmesg.enc | ./tools/kmsg/dmesg-decipher certs/signing_key.pem
+
+ [ 0.000000] Linux version 4.15.0-rc5+ (dan@jupiter) (gcc version 7.2.1 20170915 (Red Hat 7.2.1-2) (GCC)) #109 SMP Sat Dec 30 18:32:25 IST 2017
+ [ 0.000000] Command line: BOOT_IMAGE=/vmlinuz-4.15.0-rc5-dan+ root=UUID=f48b37ec-fcb8-4689-b12e-58703db3cb21 ro rhgb quiet LANG=en_US.UTF-8
+ [ 0.000000] x86/fpu: Supporting XSAVE feature 0x001: 'x87 floating point registers'
+ ...
diff --git a/Documentation/admin-guide/index.rst b/Documentation/admin-guide/index.rst
index 5bb9161dbe6a..3b0cd49c75d4 100644
--- a/Documentation/admin-guide/index.rst
+++ b/Documentation/admin-guide/index.rst
@@ -63,6 +63,7 @@ configure specific aspects of kernel behavior to your liking.
pm/index
thunderbolt
LSM/index
+ dmesg-encryption

.. only:: subproject and html

--
2.14.3

2018-01-13 21:37:21

by Dan Aloni

[permalink] [raw]
Subject: [PATCHv2 6/7] tools: add dmesg decryption program

Example execution:

dmesg | dmesg-decipher <private-key.pem>

Signed-off-by: Dan Aloni <[email protected]>
---
tools/Makefile | 9 +-
tools/kmsg/.gitignore | 1 +
tools/kmsg/Makefile | 14 ++
tools/kmsg/dmesg-decipher.c | 354 ++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 377 insertions(+), 1 deletion(-)
create mode 100644 tools/kmsg/.gitignore
create mode 100644 tools/kmsg/Makefile
create mode 100644 tools/kmsg/dmesg-decipher.c

diff --git a/tools/Makefile b/tools/Makefile
index be02c8b904db..5a661e4c9012 100644
--- a/tools/Makefile
+++ b/tools/Makefile
@@ -18,6 +18,7 @@ help:
@echo ' hv - tools used when in Hyper-V clients'
@echo ' iio - IIO tools'
@echo ' kvm_stat - top-like utility for displaying kvm statistics'
+ @echo ' kmsg - A tool for decrypting a dmesg using a private key'
@echo ' leds - LEDs tools'
@echo ' liblockdep - user-space wrapper for kernel locking-validator'
@echo ' bpf - misc BPF tools'
@@ -91,6 +92,9 @@ freefall: FORCE
kvm_stat: FORCE
$(call descend,kvm/$@)

+kmsg: FORCE
+ $(call descend,kmsg)
+
all: acpi cgroup cpupower gpio hv firewire liblockdep \
perf selftests spi turbostat usb \
virtio vm bpf x86_energy_perf_policy \
@@ -167,6 +171,9 @@ tmon_clean:
freefall_clean:
$(call descend,laptop/freefall,clean)

+kmsg_clean:
+ $(call descend,kmsg,clean)
+
build_clean:
$(call descend,build,clean)

@@ -174,6 +181,6 @@ clean: acpi_clean cgroup_clean cpupower_clean hv_clean firewire_clean \
perf_clean selftests_clean turbostat_clean spi_clean usb_clean virtio_clean \
vm_clean bpf_clean iio_clean x86_energy_perf_policy_clean tmon_clean \
freefall_clean build_clean libbpf_clean libsubcmd_clean liblockdep_clean \
- gpio_clean objtool_clean leds_clean wmi_clean
+ gpio_clean objtool_clean leds_clean wmi_clean kmsg_clean

.PHONY: FORCE
diff --git a/tools/kmsg/.gitignore b/tools/kmsg/.gitignore
new file mode 100644
index 000000000000..a5b4e26b8d0b
--- /dev/null
+++ b/tools/kmsg/.gitignore
@@ -0,0 +1 @@
+dmesg-decipher
diff --git a/tools/kmsg/Makefile b/tools/kmsg/Makefile
new file mode 100644
index 000000000000..9f4ef7b11798
--- /dev/null
+++ b/tools/kmsg/Makefile
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0
+CC := $(CROSS_COMPILE)gcc
+
+CFLAGS := -O2 -Wall $$(pkg-config --libs openssl)
+
+PROGS := dmesg-decipher
+
+%: %.c
+ $(CC) $(CFLAGS) -o $@ $^
+
+all: $(PROGS)
+
+clean:
+ rm -fr $(PROGS)
diff --git a/tools/kmsg/dmesg-decipher.c b/tools/kmsg/dmesg-decipher.c
new file mode 100644
index 000000000000..1ad2b0a27402
--- /dev/null
+++ b/tools/kmsg/dmesg-decipher.c
@@ -0,0 +1,354 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * dmesg-decipher.c
+ *
+ * A sample utility to decrypt an encrypted dmesg output, for
+ * development with kernels having kmsg encryption enabled.
+ *
+ * base64 decoding code taken from lib/base64-armor.c
+ *
+ * Copyright (c) Dan Aloni, 2017
+ *
+ * Compile with:
+ *
+ * gcc -O2 -Wall $(pkg-config --libs openssl) \
+ * dmesg-decipher -o dmesg-decipher
+ */
+
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/pkcs7.h>
+
+#include <regex.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+
+/*
+ * The following is based on code from:
+ *
+ * https://wiki.openssl.org/index.php/EVP_Authenticated_Encryption_and_Decryption
+ */
+static int aes_256_gcm_decrypt(unsigned char *ciphertext, size_t ciphertext_len,
+ unsigned char *aad, size_t aad_len,
+ unsigned char *tag, unsigned char *key,
+ unsigned char *iv, size_t iv_len,
+ unsigned char *plaintext)
+{
+ EVP_CIPHER_CTX *ctx;
+ int len;
+ int plaintext_len;
+ int ret = -1;
+
+ /* Create and initialise the context */
+ ctx = EVP_CIPHER_CTX_new();
+ if (!ctx)
+ return -1;
+
+ /* Initialise the decryption operation. */
+ if (!EVP_DecryptInit_ex(ctx, EVP_aes_128_gcm(), NULL, NULL, NULL))
+ goto free;
+
+ /* Set IV length. Not necessary if this is 12 bytes (96 bits) */
+ if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, NULL))
+ goto free;
+
+ /* Initialise key and IV */
+ if (!EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv))
+ goto free;
+
+ /* Provide any AAD data. This can be called zero or more times as
+ * required
+ */
+ if (aad_len != 0)
+ if (!EVP_DecryptUpdate(ctx, NULL, &len, aad, aad_len))
+ goto free;
+
+ /* Provide the message to be decrypted, and obtain the plaintext output.
+ * EVP_DecryptUpdate can be called multiple times if necessary
+ */
+ if (!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext,
+ ciphertext_len))
+ goto free;
+ plaintext_len = len;
+
+ /* Set expected tag value. Works in OpenSSL 1.0.1d and later */
+ if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, tag))
+ goto free;
+
+ /* Finalise the decryption. A positive return value indicates success,
+ * anything else is a failure - the plaintext is not trustworthy.
+ */
+ ret = EVP_DecryptFinal_ex(ctx, plaintext + len, &len);
+
+free:
+ /* Clean up */
+ EVP_CIPHER_CTX_free(ctx);
+
+ if (ret > 0) {
+ /* Success */
+ plaintext_len += len;
+ return plaintext_len;
+ }
+
+ /* Verify failed */
+ return -1;
+}
+
+static int decode_bits(char c)
+{
+ if (c >= 'A' && c <= 'Z')
+ return c - 'A';
+ if (c >= 'a' && c <= 'z')
+ return c - 'a' + 26;
+ if (c >= '0' && c <= '9')
+ return c - '0' + 52;
+ if (c == '+')
+ return 62;
+ if (c == '/')
+ return 63;
+ if (c == '=')
+ return 0; /* just non-negative, please */
+ return -EINVAL;
+}
+
+static int base64_unarmor(char *dst, int dst_max, const char *src,
+ const char *end)
+{
+ int olen = 0;
+
+ while (src < end) {
+ int a, b, c, d;
+
+ if (src[0] == '\n') {
+ src++;
+ continue;
+ }
+ if (src + 4 > end)
+ return -EINVAL;
+ a = decode_bits(src[0]);
+ b = decode_bits(src[1]);
+ c = decode_bits(src[2]);
+ d = decode_bits(src[3]);
+ if (a < 0 || b < 0 || c < 0 || d < 0)
+ return -EINVAL;
+
+ if (dst_max < 1)
+ return -ENOSPC;
+ *dst++ = (a << 2) | (b >> 4);
+ dst_max--;
+ if (src[2] == '=')
+ return olen + 1;
+ if (dst_max < 1)
+ return -ENOSPC;
+ *dst++ = ((b & 15) << 4) | (c >> 2);
+ dst_max--;
+ if (src[3] == '=')
+ return olen + 2;
+ if (dst_max < 1)
+ return -ENOSPC;
+ *dst++ = ((c & 3) << 6) | d;
+ dst_max--;
+ olen += 3;
+ src += 4;
+ }
+ return olen;
+}
+
+static int parse_int_regex_match(const char *source, regmatch_t match,
+ size_t *output)
+{
+ char decimal_number[0x10] = {
+ 0,
+ };
+ size_t len = match.rm_eo - match.rm_so;
+
+ if (len >= sizeof(decimal_number))
+ return -1;
+
+ memcpy(&decimal_number[0], &source[match.rm_so], len);
+
+ *output = atoi(decimal_number);
+ return 0;
+}
+
+static const char session_key_pattern[] = "(.*)K:([0-9a-zA-Z~+/=]+)";
+static const char message_pattern[] =
+ "(.*)M:([0-9a-zA-Z~+/=]+),([0-9]+),([0-9]+)";
+
+static int decrypt_message(char *line, regmatch_t *matches, uint8_t *sess_key)
+{
+ char plain_text[0x1000], *enc;
+ uint8_t cipher_msg_bin[0x1000];
+ size_t cipher_msg_size = sizeof(cipher_msg_bin);
+ size_t cipher_size;
+ const regmatch_t prefix = matches[1];
+ const regmatch_t ciphermsg = matches[2];
+ const regmatch_t auth_str_len = matches[3];
+ const regmatch_t iv_str_len = matches[4];
+ size_t auth_len;
+ size_t iv_len;
+ int ret;
+
+ ret = parse_int_regex_match(line, auth_str_len, &auth_len);
+ if (ret)
+ return -1;
+
+ ret = parse_int_regex_match(line, iv_str_len, &iv_len);
+ if (ret)
+ return -1;
+
+ for (enc = &line[ciphermsg.rm_so]; enc < &line[ciphermsg.rm_eo]; enc++)
+ if (*enc == '~')
+ *enc = '\n';
+
+ ret = base64_unarmor((char *)cipher_msg_bin, cipher_msg_size,
+ &line[ciphermsg.rm_so], &line[ciphermsg.rm_eo]);
+ if (ret < 0) {
+ fprintf(stderr, "error decoding base64 message (code = %d)\n",
+ ret);
+ return -1;
+ }
+
+ cipher_msg_size = ret;
+
+ if (iv_len >= cipher_msg_size || auth_len >= cipher_msg_size
+ || auth_len + iv_len > cipher_msg_size) {
+ return -1;
+ }
+
+ cipher_size = cipher_msg_size - auth_len - iv_len;
+
+ ret = aes_256_gcm_decrypt(/* Ciphertext */
+ (uint8_t *)cipher_msg_bin, cipher_size,
+
+ /* AAD */
+ NULL, 0,
+
+ /* tag */
+ (uint8_t *)&cipher_msg_bin[cipher_size],
+
+ /* key */
+ sess_key,
+
+ /* IV */
+ (uint8_t *)&cipher_msg_bin[cipher_size
+ + auth_len],
+ iv_len,
+
+ /* Plain text */
+ (uint8_t *)plain_text);
+ if (ret > 0) {
+ fwrite(line, prefix.rm_eo, 1, stdout);
+ fwrite(plain_text, ret, 1, stdout);
+ fwrite("\n", 1, 1, stdout);
+ }
+
+ return ret;
+}
+
+int main(int argc, char **argv)
+{
+ BIO *tbio = NULL;
+ RSA *rsa;
+ int ret = 1;
+ char line[0x1000];
+ uint8_t enc_sess_key[0x200];
+ uint8_t sess_key[0x200] = {
+ 0,
+ };
+ bool got_key = false;
+
+ OpenSSL_add_all_algorithms();
+ ERR_load_crypto_strings();
+
+ regex_t session_key_regex;
+ regex_t message_regex;
+
+ ret = regcomp(&session_key_regex, session_key_pattern, REG_EXTENDED);
+ if (ret)
+ goto err;
+
+ ret = regcomp(&message_regex, message_pattern, REG_EXTENDED);
+ if (ret)
+ goto err;
+
+ if (argc < 2) {
+ fprintf(stderr, "not enough parameters\n");
+ return -1;
+ }
+
+ /* Read in recipient certificate and private key */
+ tbio = BIO_new_file(argv[1], "r");
+ if (!tbio) {
+ fprintf(stderr, "BIO_new_file - error\n");
+ goto err;
+ }
+
+ rsa = PEM_read_bio_RSAPrivateKey(tbio, NULL, NULL, NULL);
+ if (!rsa)
+ goto err;
+
+ while (true) {
+ regmatch_t matches[5];
+
+ if (!fgets(line, sizeof(line), stdin))
+ break;
+
+ if (!got_key
+ && !regexec(&session_key_regex, line, 5, matches, 0)) {
+ const regmatch_t match = matches[2];
+ size_t enc_sess_key_size = sizeof(enc_sess_key);
+ char *enc;
+
+ for (enc = &line[match.rm_so]; enc < &line[match.rm_eo];
+ enc++)
+ if (*enc == '~')
+ *enc = '\n';
+
+ ret = base64_unarmor(
+ (char *)&enc_sess_key, enc_sess_key_size,
+ &line[match.rm_so], &line[match.rm_eo]);
+ if (ret < 0) {
+ fprintf(stderr,
+ "error decoding session key"
+ " (code = %d)\n",
+ ret);
+ return -1;
+ }
+
+ enc_sess_key_size = ret;
+
+ ret = RSA_private_decrypt(enc_sess_key_size,
+ enc_sess_key, sess_key, rsa,
+ RSA_PKCS1_PADDING);
+ if (ret < 0)
+ goto err;
+
+ got_key = true;
+ }
+
+ if (!regexec(&message_regex, line, 5, matches, 0)) {
+ if (!got_key) {
+ fprintf(stderr,
+ "session key must precede messages\n");
+ break;
+ }
+
+ ret = decrypt_message(line, matches, sess_key);
+ if (ret < 0) {
+ fprintf(stderr,
+ "error decrypting message"
+ " (code = %d)\n",
+ ret);
+ break;
+ }
+ }
+ }
+
+ regfree(&session_key_regex);
+ regfree(&message_regex);
+
+err:
+ return -1;
+}
--
2.14.3

2018-01-13 21:37:26

by Dan Aloni

[permalink] [raw]
Subject: [PATCHv2 3/7] base64-armor: add bounds checking

Future use of the API can benefit from bounds checking.

Signed-off-by: Dan Aloni <[email protected]>
---
include/linux/base64-armor.h | 17 +++++++++++------
lib/base64-armor.c | 20 ++++++++++++++++++--
net/ceph/crypto.c | 2 +-
3 files changed, 30 insertions(+), 9 deletions(-)

diff --git a/include/linux/base64-armor.h b/include/linux/base64-armor.h
index e5160c77bb2f..bb0b4491799e 100644
--- a/include/linux/base64-armor.h
+++ b/include/linux/base64-armor.h
@@ -8,11 +8,13 @@
* not contain newlines, depending on input length.
*
* @dst: Beginning of the destination buffer.
+ * @dst_max: Maximum amount of bytes to write to the destination buffer.
* @src: Beginning of the source buffer.
* @end: Sentinel for the source buffer, pointing one byte after the
* last byte to be encoded.
*
- * Returns the number of bytes written to the destination buffer.
+ * Returns the number of bytes written to the destination buffer, or
+ * an error of the output buffer is insufficient in size.
*
* _Neither_ the input or output are expected to be NULL-terminated.
*
@@ -22,19 +24,21 @@
*
* See base64_encode_buffer_bound below.
*/
-
-extern int base64_armor(char *dst, const char *src, const char *end);
+extern int base64_armor(char *dst, int dst_max, const char *src,
+ const char *end);

/**
* base64_unarmor: Perform armored base64 decoding.
*
* @dst: Beginning of the destination buffer.
+ * @dst_max: Maximum amount of bytes to write to the destination buffer.
* @src: Beginning of the source buffer
* @end: Sentinel for the source buffer, pointing one byte after the
* last byte to be encoded.
*
- * Returns the number of bytes written to the destination buffer, or
- * -EINVAL if the source buffer contains invalid bytes.
+ * Returns the number of bytes written to the destination buffer,
+ * -EINVAL if the source buffer contains invalid bytes, or -ENOSPC
+ * if the output buffer is insufficient in size.
*
* _Neither_ the input or output are expected to be NULL-terminated.
*
@@ -43,7 +47,8 @@ extern int base64_armor(char *dst, const char *src, const char *end);
*
* See base64_decode_buffer_bound below.
*/
-extern int base64_unarmor(char *dst, const char *src, const char *end);
+extern int base64_unarmor(char *dst, int dst_max, const char *src,
+ const char *end);


/*
diff --git a/lib/base64-armor.c b/lib/base64-armor.c
index e07d25ac2850..f4a289f8da6a 100644
--- a/lib/base64-armor.c
+++ b/lib/base64-armor.c
@@ -33,7 +33,7 @@ static int decode_bits(char c)
return -EINVAL;
}

-int base64_armor(char *dst, const char *src, const char *end)
+int base64_armor(char *dst, int dst_max, const char *src, const char *end)
{
int olen = 0;
int line = 0;
@@ -42,6 +42,8 @@ int base64_armor(char *dst, const char *src, const char *end)
unsigned char a, b, c;

a = *src++;
+ if (dst_max < 4)
+ return -ENOSPC;
*dst++ = encode_bits(a >> 2);
if (src < end) {
b = *src++;
@@ -62,17 +64,22 @@ int base64_armor(char *dst, const char *src, const char *end)
}
olen += 4;
line += 4;
+ dst_max -= 4;
+
if (line == 64) {
line = 0;
+ if (dst_max < 1)
+ return -ENOSPC;
*(dst++) = '\n';
olen++;
+ dst_max--;
}
}
return olen;
}
EXPORT_SYMBOL(base64_unarmor);

-int base64_unarmor(char *dst, const char *src, const char *end)
+int base64_unarmor(char *dst, int dst_max, const char *src, const char *end)
{
int olen = 0;

@@ -92,13 +99,22 @@ int base64_unarmor(char *dst, const char *src, const char *end)
if (a < 0 || b < 0 || c < 0 || d < 0)
return -EINVAL;

+ if (dst_max < 1)
+ return -ENOSPC;
*dst++ = (a << 2) | (b >> 4);
+ dst_max--;
if (src[2] == '=')
return olen + 1;
+ if (dst_max < 1)
+ return -ENOSPC;
*dst++ = ((b & 15) << 4) | (c >> 2);
+ dst_max--;
if (src[3] == '=')
return olen + 2;
+ if (dst_max < 1)
+ return -ENOSPC;
*dst++ = ((c & 3) << 6) | d;
+ dst_max--;
olen += 3;
src += 4;
}
diff --git a/net/ceph/crypto.c b/net/ceph/crypto.c
index 25e04e3b1aa4..f7c75368989a 100644
--- a/net/ceph/crypto.c
+++ b/net/ceph/crypto.c
@@ -116,7 +116,7 @@ int ceph_crypto_key_unarmor(struct ceph_crypto_key *key, const char *inkey)
buf = kmalloc(blen, GFP_NOFS);
if (!buf)
return -ENOMEM;
- blen = base64_unarmor(buf, inkey, inkey+inlen);
+ blen = base64_unarmor(buf, blen, inkey, inkey+inlen);
if (blen < 0) {
kfree(buf);
return blen;
--
2.14.3

2018-01-13 21:37:57

by Dan Aloni

[permalink] [raw]
Subject: [PATCHv2 2/7] Move net/ceph/armor to lib/ and add docs

Plus, add functions that assist in managing buffer bounds.

Signed-off-by: Dan Aloni <[email protected]>
---
include/linux/base64-armor.h | 65 ++++++++++++++++++++++++++++++++++
lib/Kconfig | 7 ++++
lib/Makefile | 1 +
net/ceph/armor.c => lib/base64-armor.c | 13 ++++---
net/ceph/Kconfig | 1 +
net/ceph/Makefile | 2 +-
net/ceph/crypto.c | 3 +-
net/ceph/crypto.h | 4 ---
8 files changed, 85 insertions(+), 11 deletions(-)
create mode 100644 include/linux/base64-armor.h
rename net/ceph/armor.c => lib/base64-armor.c (86%)

diff --git a/include/linux/base64-armor.h b/include/linux/base64-armor.h
new file mode 100644
index 000000000000..e5160c77bb2f
--- /dev/null
+++ b/include/linux/base64-armor.h
@@ -0,0 +1,65 @@
+#ifndef __LINUX_BASE64_ARMOR_H__
+#define __LINUX_BASE64_ARMOR_H__
+
+#include <linux/types.h>
+
+/**
+ * base64_armor: Perform armored base64 encoding. Output may or may
+ * not contain newlines, depending on input length.
+ *
+ * @dst: Beginning of the destination buffer.
+ * @src: Beginning of the source buffer.
+ * @end: Sentinel for the source buffer, pointing one byte after the
+ * last byte to be encoded.
+ *
+ * Returns the number of bytes written to the destination buffer.
+ *
+ * _Neither_ the input or output are expected to be NULL-terminated.
+ *
+ * The number of output bytes is exactly (n * 4 + (n / 16)) where
+ * n = ((end - src) + 2) / 3. A less stringent but more wasteful
+ * validation for output buffer size can be: 4 + (end - src) * 2.
+ *
+ * See base64_encode_buffer_bound below.
+ */
+
+extern int base64_armor(char *dst, const char *src, const char *end);
+
+/**
+ * base64_unarmor: Perform armored base64 decoding.
+ *
+ * @dst: Beginning of the destination buffer.
+ * @src: Beginning of the source buffer
+ * @end: Sentinel for the source buffer, pointing one byte after the
+ * last byte to be encoded.
+ *
+ * Returns the number of bytes written to the destination buffer, or
+ * -EINVAL if the source buffer contains invalid bytes.
+ *
+ * _Neither_ the input or output are expected to be NULL-terminated.
+ *
+ * It can be assumed that the number of output bytes is less or
+ * equals to: 3 * ((end - src) / 4).
+ *
+ * See base64_decode_buffer_bound below.
+ */
+extern int base64_unarmor(char *dst, const char *src, const char *end);
+
+
+/*
+ * Utility functions for buffer upper bounds:
+ */
+
+static inline size_t base64_encode_buffer_bound(size_t src_len)
+{
+ size_t n = (src_len + 2) / 3;
+
+ return (n * 4 + (n / 16));
+}
+
+static inline size_t base64_decode_buffer_bound(size_t src_len)
+{
+ return 3 * (src_len / 4);
+}
+
+#endif
diff --git a/lib/Kconfig b/lib/Kconfig
index c5e84fbcb30b..caddcaebbc2f 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -188,6 +188,13 @@ config CRC8
when they need to do cyclic redundancy check according CRC8
algorithm. Module will be called crc8.

+config BASE64_ARMOR
+ tristate "BASE64 encoding/decoding functions"
+ help
+ This option provides BASE64 encoding and decoding functions.
+ Module name will be base64-armor if this code is built as a
+ module.
+
config XXHASH
tristate

diff --git a/lib/Makefile b/lib/Makefile
index d11c48ec8ffd..47335d28f77f 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -94,6 +94,7 @@ ifneq ($(CONFIG_HAVE_DEC_LOCK),y)
lib-y += dec_and_lock.o
endif

+obj-$(CONFIG_BASE64_ARMOR) += base64-armor.o
obj-$(CONFIG_BITREVERSE) += bitrev.o
obj-$(CONFIG_RATIONAL) += rational.o
obj-$(CONFIG_CRC_CCITT) += crc-ccitt.o
diff --git a/net/ceph/armor.c b/lib/base64-armor.c
similarity index 86%
rename from net/ceph/armor.c
rename to lib/base64-armor.c
index 0db8065928df..e07d25ac2850 100644
--- a/net/ceph/armor.c
+++ b/lib/base64-armor.c
@@ -1,9 +1,8 @@
// SPDX-License-Identifier: GPL-2.0

#include <linux/errno.h>
-
-int ceph_armor(char *dst, const char *src, const char *end);
-int ceph_unarmor(char *dst, const char *src, const char *end);
+#include <linux/module.h>
+#include <linux/base64-armor.h>

/*
* base64 encode/decode.
@@ -34,7 +33,7 @@ static int decode_bits(char c)
return -EINVAL;
}

-int ceph_armor(char *dst, const char *src, const char *end)
+int base64_armor(char *dst, const char *src, const char *end)
{
int olen = 0;
int line = 0;
@@ -71,8 +70,9 @@ int ceph_armor(char *dst, const char *src, const char *end)
}
return olen;
}
+EXPORT_SYMBOL(base64_unarmor);

-int ceph_unarmor(char *dst, const char *src, const char *end)
+int base64_unarmor(char *dst, const char *src, const char *end)
{
int olen = 0;

@@ -104,3 +104,6 @@ int ceph_unarmor(char *dst, const char *src, const char *end)
}
return olen;
}
+EXPORT_SYMBOL(base64_armor);
+
+MODULE_LICENSE("GPL v2");
diff --git a/net/ceph/Kconfig b/net/ceph/Kconfig
index f8cceb99e732..5c4e7d0f2896 100644
--- a/net/ceph/Kconfig
+++ b/net/ceph/Kconfig
@@ -2,6 +2,7 @@ config CEPH_LIB
tristate "Ceph core library"
depends on INET
select LIBCRC32C
+ select BASE64_ARMOR
select CRYPTO_AES
select CRYPTO_CBC
select CRYPTO
diff --git a/net/ceph/Makefile b/net/ceph/Makefile
index b4bded4b5396..bf6f5e34bdb1 100644
--- a/net/ceph/Makefile
+++ b/net/ceph/Makefile
@@ -10,7 +10,7 @@ libceph-y := ceph_common.o messenger.o msgpool.o buffer.o pagelist.o \
osd_client.o osdmap.o crush/crush.o crush/mapper.o crush/hash.o \
debugfs.o \
auth.o auth_none.o \
- crypto.o armor.o \
+ crypto.o \
auth_x.o \
ceph_fs.o ceph_strings.o ceph_hash.o \
pagevec.o snapshot.o string_table.o
diff --git a/net/ceph/crypto.c b/net/ceph/crypto.c
index bf9d079cbafd..25e04e3b1aa4 100644
--- a/net/ceph/crypto.c
+++ b/net/ceph/crypto.c
@@ -10,6 +10,7 @@
#include <crypto/skcipher.h>
#include <linux/key-type.h>
#include <linux/sched/mm.h>
+#include <linux/base64-armor.h>

#include <keys/ceph-type.h>
#include <keys/user-type.h>
@@ -115,7 +116,7 @@ int ceph_crypto_key_unarmor(struct ceph_crypto_key *key, const char *inkey)
buf = kmalloc(blen, GFP_NOFS);
if (!buf)
return -ENOMEM;
- blen = ceph_unarmor(buf, inkey, inkey+inlen);
+ blen = base64_unarmor(buf, inkey, inkey+inlen);
if (blen < 0) {
kfree(buf);
return blen;
diff --git a/net/ceph/crypto.h b/net/ceph/crypto.h
index bb45c7d43739..a0236cbad530 100644
--- a/net/ceph/crypto.h
+++ b/net/ceph/crypto.h
@@ -29,8 +29,4 @@ int ceph_crypt(const struct ceph_crypto_key *key, bool encrypt,
int ceph_crypto_init(void);
void ceph_crypto_shutdown(void);

-/* armor.c */
-int ceph_armor(char *dst, const char *src, const char *end);
-int ceph_unarmor(char *dst, const char *src, const char *end);
-
#endif
--
2.14.3

2018-01-14 01:48:07

by Sergey Senozhatsky

[permalink] [raw]
Subject: Re: [PATCHv2 5/7] printk: allow kmsg to be encrypted using public key encryption

Ccing Kees, Peter, Andrew, Steven

On (01/13/18 23:34), Dan Aloni wrote:
> 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.

OK, this is the first time I'm receiving it, yet it's v2 already.
I'm Cc-ed on only this particular patch, not the entire patch set;
so it's hard to tell what else is being touched and why, so I'm
going to start with the basic questions.

are you fixing the real problem? that's because you see unhashed
kernel pointers in dmesg or is there anything else?

-ss

// keeping the code for Cc-ed people

> ---
> 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:[email protected]>
> '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
>

2018-01-14 08:02:53

by Dan Aloni

[permalink] [raw]
Subject: Re: [PATCHv2 5/7] printk: allow kmsg to be encrypted using public key encryption

On Sun, Jan 14, 2018 at 10:48:01AM +0900, Sergey Senozhatsky wrote:
> Ccing Kees, Peter, Andrew, Steven
>
> On (01/13/18 23:34), Dan Aloni wrote:
> > 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.
>
> OK, this is the first time I'm receiving it, yet it's v2 already.
> I'm Cc-ed on only this particular patch, not the entire patch set;
> so it's hard to tell what else is being touched and why, so I'm
> going to start with the basic questions.

Sorry, here the link to cover letter:

https://www.mail-archive.com/[email protected]/msg1585442.html

I guess --cc-cover && --to-cover should be default.

> are you fixing the real problem? that's because you see unhashed
> kernel pointers in dmesg or is there anything else?

In brief, the problem is that any information leak has the potential
to make exploitation easier. The changes include documentation for
the feature, where more details are provided.

--
Dan Aloni

2018-01-15 09:11:34

by David Howells

[permalink] [raw]
Subject: Re: [PATCHv2 4/7] certs: allow in-kernel access of trusted keys

Hi Dan,

The patch needs a description saying what you're doing and why.

David

2018-01-15 12:55:55

by Steven Rostedt

[permalink] [raw]
Subject: Re: [PATCHv2 5/7] printk: allow kmsg to be encrypted using public key encryption

On Sun, 14 Jan 2018 10:01:08 +0200
Dan Aloni <[email protected]> wrote:

> On Sun, Jan 14, 2018 at 10:48:01AM +0900, Sergey Senozhatsky wrote:
> > Ccing Kees, Peter, Andrew, Steven
> >
> > On (01/13/18 23:34), Dan Aloni wrote:
> > > 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.
> >
> > OK, this is the first time I'm receiving it, yet it's v2 already.
> > I'm Cc-ed on only this particular patch, not the entire patch set;
> > so it's hard to tell what else is being touched and why, so I'm
> > going to start with the basic questions.
>
> Sorry, here the link to cover letter:
>
> https://www.mail-archive.com/[email protected]/msg1585442.html
>
> I guess --cc-cover && --to-cover should be default.
>
> > are you fixing the real problem? that's because you see unhashed
> > kernel pointers in dmesg or is there anything else?
>
> In brief, the problem is that any information leak has the potential
> to make exploitation easier. The changes include documentation for
> the feature, where more details are provided.
>

I'm very skeptical that such an approach has much benefit. From the
email referenced above:

> I am not sure that desktop and power users would like to have their
> kernel message encrypted, but there are scenarios such as in mobile
> devices, where only the developers, makers of devices, may actually
> benefit from access to kernel prints messages, and the users may be
> more protected from exploits.

Do you have any backing from makers of such devices? I'd like to hear
from Google's Android team or whoever that would turn this feature on.

I would be hard pressed to add such a feature if it's never used.

-- Steve

2018-01-16 02:09:51

by Sergey Senozhatsky

[permalink] [raw]
Subject: Re: [PATCHv2 5/7] printk: allow kmsg to be encrypted using public key encryption

On (01/15/18 07:52), Steven Rostedt wrote:
[..]
> I'm very skeptical that such an approach has much benefit. From the
> email referenced above:

agreed. dmesg can be SECURITY_DMESG_RESTRICT.
so the patch is *probably* aiming the systems in which anyone can read
dmesg, but we kinda don't want that to happen. may be I'm missing the
point.

> > I am not sure that desktop and power users would like to have their
> > kernel message encrypted, but there are scenarios such as in mobile
> > devices, where only the developers, makers of devices, may actually
> > benefit from access to kernel prints messages, and the users may be
> > more protected from exploits.
>
> Do you have any backing from makers of such devices? I'd like to hear
> from Google's Android team or whoever that would turn this feature on.
>
> I would be hard pressed to add such a feature if it's never used.

right.

-ss

2018-01-16 23:44:48

by Daniel Micay

[permalink] [raw]
Subject: Re: [kernel-hardening] Re: [PATCHv2 5/7] printk: allow kmsg to be encrypted using public key encryption

> Do you have any backing from makers of such devices? I'd like to hear
> from Google's Android team or whoever that would turn this feature on.

(I'm not a Google employee, but I work on Android security and
contribute some of that to AOSP.)

Android restricts access to dmesg with SELinux, so it's not really an
issue there. They previously used dmesg_restrict but switched to
SELinux for fine-grained control without using the capability. In
general, the access control features / toggles proposed on
kernel-hardening don't do much for Android because there's nothing
unconfined and there's no system administrator reconfiguring the
system and disabling SELinux to make it work. The toggles like
dmesg_restrict tend to be too coarse without a good way to make
exceptions.

Unprivileged processes including apps can't access dmesg, debugfs,
sysfs (other than a tiny set of exceptions), procfs outside of
/proc/net and /proc/PID (but it uses hidepid=2) and a few whitelisted
files, etc. That's mostly done with SELinux along with using it for
ioctl command whitelisting to have per-device per-domain whitelists of
commands instead of using their global seccomp-bpf policy for it. The
hidepid=2 usage is an example of a feature where SELinux doesn't quite
fit the task as an alternative since apps are all in the untrusted_app
/ isolated_app domains but each have a unique uid/gid. It works
because hidepid has a gid option to set fine-grained exceptions,
rather than only being able to bypass it with a capability.

They do have some out-of-tree access control features and finding ways
to upstream those might be useful:

There's the added perf_event_paranoid=3 level which is set by default.
It's toggled off when a developer using profiling tools via Android
Debug Bridge (USB) after enabling ADB support in the OS and
whitelisting their key. That was submitted upstream again for Android
but it didn't end up landing. An LSM hook for perf events might help
but Android uses a fully static SELinux policy and would need a way to
toggle it off / on.

They also have group-based control over sockets used to implement the
INTERNET permission but technically they could double the number of
app domains and use SELinux for that if they absolutely had to avoid
an out-of-tree patch. They might not design it the same way today
since it predates using SELinux.

2018-01-17 15:01:08

by Steven Rostedt

[permalink] [raw]
Subject: Re: [kernel-hardening] Re: [PATCHv2 5/7] printk: allow kmsg to be encrypted using public key encryption

On Tue, 16 Jan 2018 18:44:45 -0500
Daniel Micay <[email protected]> wrote:

> > Do you have any backing from makers of such devices? I'd like to hear
> > from Google's Android team or whoever that would turn this feature on.
>
> (I'm not a Google employee, but I work on Android security and
> contribute some of that to AOSP.)
>
> Android restricts [ delete what Android does ]

That doesn't answer the question if Android would enable this feature
or not. With what you show, it sounds like they wouldn't, as they are
already covered.

-- Steve