From patchwork Sat Dec 30 17:58:02 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dan Aloni X-Patchwork-Id: 10137861 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 4451960375 for ; Sat, 30 Dec 2017 17:58:53 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 35219287A1 for ; Sat, 30 Dec 2017 17:58:53 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 29B90287A3; Sat, 30 Dec 2017 17:58:53 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.4 required=2.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_MED, T_DKIM_INVALID, URIBL_BLACK autolearn=ham version=3.3.1 Received: from mother.openwall.net (mother.openwall.net [195.42.179.200]) by mail.wl.linuxfoundation.org (Postfix) with SMTP id 41B22287A1 for ; Sat, 30 Dec 2017 17:58:51 +0000 (UTC) Received: (qmail 15602 invoked by uid 550); 30 Dec 2017 17:58:26 -0000 Mailing-List: contact kernel-hardening-help@lists.openwall.com; run by ezmlm Precedence: bulk List-Post: List-Help: List-Unsubscribe: List-Subscribe: List-ID: Delivered-To: mailing list kernel-hardening@lists.openwall.com Received: (qmail 15441 invoked from network); 30 Dec 2017 17:58:24 -0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernelim-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=Uyz9EUAOfrFUlmtPqpyrDfuaBplIW3lG5UTWOQsO5bs=; b=T+oTcpQMpqrcgf3ub6SNKm6W1kpL8S4GlLVt5vFvt+JK57ZglhZEea83BKoh3zmjIu tFY6doDsORLhpsCSJhIlwvA+ibfTVQzSMwnD8l0vO5FCsCGBCIBEhorwYx+htVbAq96S F2nryVOYDyDGhbJ3kg1kfxS91yzK20lbAezVzCFBSrKH/JznVs/QdG7kOSy75kxaYS/5 Zchl/LvbxJA5XtBFhoeiEFXnr5RhhmTXBjPKrNWh++u4XukubWDutg/X8qqX68PJNUz+ 97iGiH3inRIEhlh7d5fMBGFB3i+J76LithnBKhY5/gErJUlIimwaTPqsAPxbn/MAe4UE WAZw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=Uyz9EUAOfrFUlmtPqpyrDfuaBplIW3lG5UTWOQsO5bs=; b=hmXjP8pNcU/qrHwA1qa37JwK9iOOLRdMir+sb7tzmmxelUbphabxTMGYdHK7V8V/5A OxFv6uelLv1558qrQ4kGftEDFRnUJeQtLPQNvu6Q7D/wdmKy1/S7ywo8sOEIUacU2Hd8 mwsRh8WwF+NKVzFGyf3xyesLLKl+8HcgBWC+AbbpwJUv9NwrL8YJmMqbVUkgyfwkwL3Q Prw1AGfRHgqjtz/0+7bGRqadcycn7v8sdjo1xn5RPTups+GsW1X5yhwJ3v14qlMVKF/A kMDqUdQ0/54rQ9BeCrSxqtMDKONEkOODxPi64u+T2tg/ggh/gqXmm8p3TVOGaIoka2hU TOeg== X-Gm-Message-State: AKGB3mLbr/6lBklUlFtPpXbr6DYB/otexaMdJeUbAaZnJxnFZj+70/oI 8O7TpUX0Hh38kljOSz35hUZHAZic X-Google-Smtp-Source: ACJfBosyn/UHO3K1UvukGhpmyXh60fgpVpQJPyqzZapNUU5EozEAXvaJuNfZ8ZbHAT0CMY/nXn/gFA== X-Received: by 10.223.132.129 with SMTP id 1mr37596234wrg.218.1514656693014; Sat, 30 Dec 2017 09:58:13 -0800 (PST) From: Dan Aloni X-Google-Original-From: Dan Aloni To: linux-kernel@vger.kernel.org, kernel-hardening@lists.openwall.com Cc: Dan Aloni Date: Sat, 30 Dec 2017 19:58:02 +0200 Message-Id: <20171230175804.7354-4-alonid@gmail.com> X-Mailer: git-send-email 2.13.6 In-Reply-To: <20171230175804.7354-1-alonid@gmail.com> References: <20171230175804.7354-1-alonid@gmail.com> Subject: [kernel-hardening] [PATCH 3/5] kernel/printk: allow kmsg to be encrypted using public key encryption X-Virus-Scanned: ClamAV using ClamSMTP From: Dan Aloni 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 --- 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 +#include + +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 #include #include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -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(¶ms, (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, ¶ms, 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 */