From patchwork Fri Jul 17 23:09:30 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Deven Bowers X-Patchwork-Id: 11671409 X-Patchwork-Delegate: snitzer@redhat.com Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id EA0A513A4 for ; Fri, 17 Jul 2020 23:21:07 +0000 (UTC) Received: from us-smtp-delivery-1.mimecast.com (us-smtp-1.mimecast.com [205.139.110.61]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id A00902064C for ; Fri, 17 Jul 2020 23:21:07 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org A00902064C Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=linux.microsoft.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=dm-devel-bounces@redhat.com Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-78-Ji6VXLjrPVyeg2zJucx33w-1; Fri, 17 Jul 2020 19:21:03 -0400 X-MC-Unique: Ji6VXLjrPVyeg2zJucx33w-1 Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.phx2.redhat.com [10.5.11.22]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id B6766100A61E; Fri, 17 Jul 2020 23:20:58 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.20]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 9C20210013C0; Fri, 17 Jul 2020 23:20:58 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id 757A81809561; Fri, 17 Jul 2020 23:20:58 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx06.intmail.prod.int.rdu2.redhat.com [10.11.54.6]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id 06HNGmXb002240 for ; Fri, 17 Jul 2020 19:16:49 -0400 Received: by smtp.corp.redhat.com (Postfix) id DA7532156A59; Fri, 17 Jul 2020 23:16:48 +0000 (UTC) Delivered-To: dm-devel@redhat.com Received: from mimecast-mx02.redhat.com (mimecast05.extmail.prod.ext.rdu2.redhat.com [10.11.55.21]) by smtp.corp.redhat.com (Postfix) with ESMTPS id D55E92156A51 for ; Fri, 17 Jul 2020 23:16:46 +0000 (UTC) Received: from us-smtp-1.mimecast.com (us-smtp-2.mimecast.com [207.211.31.81]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 8C9A8924907 for ; Fri, 17 Jul 2020 23:16:46 +0000 (UTC) Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by relay.mimecast.com with ESMTP id us-mta-419-gZI0mygxObmUikWUPt1Iug-1; Fri, 17 Jul 2020 19:16:42 -0400 X-MC-Unique: gZI0mygxObmUikWUPt1Iug-1 Received: from dede-linux-virt.corp.microsoft.com (unknown [131.107.160.54]) by linux.microsoft.com (Postfix) with ESMTPSA id 7236C20B490A; Fri, 17 Jul 2020 16:09:46 -0700 (PDT) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com 7236C20B490A From: Deven Bowers To: agk@redhat.com, axboe@kernel.dk, snitzer@redhat.com, jmorris@namei.org, serge@hallyn.com, zohar@linux.ibm.com, viro@zeniv.linux.org.uk, paul@paul-moore.com, eparis@redhat.com, jannh@google.com, dm-devel@redhat.com, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, linux-fsdevel@vger.kernel.org, linux-block@vger.kernel.org, linux-audit@redhat.com Date: Fri, 17 Jul 2020 16:09:30 -0700 Message-Id: <20200717230941.1190744-2-deven.desai@linux.microsoft.com> In-Reply-To: <20200717230941.1190744-1-deven.desai@linux.microsoft.com> References: <20200717230941.1190744-1-deven.desai@linux.microsoft.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.78 on 10.11.54.6 X-MIME-Autoconverted: from quoted-printable to 8bit by lists01.pubmisc.prod.ext.phx2.redhat.com id 06HNGmXb002240 X-loop: dm-devel@redhat.com Cc: sashal@kernel.org, mdsakib@microsoft.com, corbet@lwn.net, linux-kernel@vger.kernel.org, pasha.tatshin@soleen.com, nramas@linux.microsoft.com, tyhicks@linux.microsoft.com, jaskarankhurana@linux.microsoft.com Subject: [dm-devel] [RFC PATCH v4 01/12] scripts: add ipe tooling to generate boot policy X-BeenThere: dm-devel@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: device-mapper development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: dm-devel-bounces@redhat.com Errors-To: dm-devel-bounces@redhat.com X-Scanned-By: MIMEDefang 2.84 on 10.5.11.22 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=dm-devel-bounces@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Add a tool for the generation of an IPE policy to be compiled into the kernel. This policy will be enforced until userland deploys and activates a new policy. Signed-off-by: Deven Bowers --- MAINTAINERS | 6 ++ scripts/Makefile | 1 + scripts/ipe/Makefile | 2 + scripts/ipe/polgen/.gitignore | 1 + scripts/ipe/polgen/Makefile | 7 ++ scripts/ipe/polgen/polgen.c | 136 ++++++++++++++++++++++++++++++++++ 6 files changed, 153 insertions(+) create mode 100644 scripts/ipe/Makefile create mode 100644 scripts/ipe/polgen/.gitignore create mode 100644 scripts/ipe/polgen/Makefile create mode 100644 scripts/ipe/polgen/polgen.c diff --git a/MAINTAINERS b/MAINTAINERS index b4a43a9e7fbc..5149c130feb0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8580,6 +8580,12 @@ S: Supported T: git git://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity.git F: security/integrity/ima/ +INTEGRITY POLICY ENFORCEMENT (IPE) +M: Deven Bowers +L: linux-integrity@vger.kernel.org +S: Supported +F: scripts/ipe/ + INTEL 810/815 FRAMEBUFFER DRIVER M: Antonino Daplas L: linux-fbdev@vger.kernel.org diff --git a/scripts/Makefile b/scripts/Makefile index 95ecf970c74c..b3c1882fd6dd 100644 --- a/scripts/Makefile +++ b/scripts/Makefile @@ -34,6 +34,7 @@ hostprogs += unifdef subdir-$(CONFIG_GCC_PLUGINS) += gcc-plugins subdir-$(CONFIG_MODVERSIONS) += genksyms subdir-$(CONFIG_SECURITY_SELINUX) += selinux +subdir-$(CONFIG_SECURITY_IPE) += ipe # Let clean descend into subdirs subdir- += basic dtc gdb kconfig mod diff --git a/scripts/ipe/Makefile b/scripts/ipe/Makefile new file mode 100644 index 000000000000..e87553fbb8d6 --- /dev/null +++ b/scripts/ipe/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +subdir-y := polgen diff --git a/scripts/ipe/polgen/.gitignore b/scripts/ipe/polgen/.gitignore new file mode 100644 index 000000000000..80f32f25d200 --- /dev/null +++ b/scripts/ipe/polgen/.gitignore @@ -0,0 +1 @@ +polgen diff --git a/scripts/ipe/polgen/Makefile b/scripts/ipe/polgen/Makefile new file mode 100644 index 000000000000..a519b594e13c --- /dev/null +++ b/scripts/ipe/polgen/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +hostprogs-y := polgen +HOST_EXTRACFLAGS += \ + -I$(srctree)/include \ + -I$(srctree)/include/uapi \ + +always := $(hostprogs-y) diff --git a/scripts/ipe/polgen/polgen.c b/scripts/ipe/polgen/polgen.c new file mode 100644 index 000000000000..a80fffe1b27c --- /dev/null +++ b/scripts/ipe/polgen/polgen.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ + +#include +#include +#include +#include +#include + +static void usage(const char *const name) +{ + printf("Usage: %s OutputFile (PolicyFile)\n", name); + exit(EINVAL); +} + +static int policy_to_buffer(const char *pathname, char **buffer, size_t *size) +{ + int rc = 0; + FILE *fd; + char *lbuf; + size_t fsize; + size_t read; + + fd = fopen(pathname, "r"); + if (!fd) { + rc = errno; + goto out; + } + + fseek(fd, 0, SEEK_END); + fsize = ftell(fd); + rewind(fd); + + lbuf = malloc(fsize); + if (!lbuf) { + rc = ENOMEM; + goto out_close; + } + + read = fread((void *)lbuf, sizeof(*lbuf), fsize, fd); + if (read != fsize) { + rc = -1; + goto out_free; + } + + *buffer = lbuf; + *size = fsize; + fclose(fd); + + return rc; + +out_free: + free(lbuf); +out_close: + fclose(fd); +out: + return rc; +} + +static int write_boot_policy(const char *pathname, const char *buf, size_t size) +{ + FILE *fd; + size_t i; + + fd = fopen(pathname, "w"); + if (!fd) + goto err; + + fprintf(fd, "/* This file is automatically generated."); + fprintf(fd, " Do not edit. */\n"); + fprintf(fd, "#include \n"); + fprintf(fd, "const char *const ipe_boot_policy =\n"); + + if (!buf || size == 0) { + fprintf(fd, "\tNULL;\n"); + fclose(fd); + return 0; + } + + for (i = 0; i < size; ++i) { + if (i == 0) + fprintf(fd, "\t\""); + + switch (buf[i]) { + case '"': + fprintf(fd, "\\\""); + break; + case '\'': + fprintf(fd, "'"); + break; + case '\n': + fprintf(fd, "\\n\"\n\t\""); + break; + case '\\': + fprintf(fd, "\\\\"); + break; + default: + fprintf(fd, "%c", buf[i]); + } + } + fprintf(fd, "\";\n"); + fclose(fd); + + return 0; + +err: + if (fd) + fclose(fd); + return errno; +} + +int main(int argc, const char *argv[]) +{ + int rc = 0; + size_t len = 0; + char *policy = NULL; + + if (argc < 2) + usage(argv[0]); + + if (argc > 2) { + rc = policy_to_buffer(argv[2], &policy, &len); + if (rc != 0) + goto cleanup; + } + + rc = write_boot_policy(argv[1], policy, len); +cleanup: + if (policy) + free(policy); + if (rc != 0) + perror("An error occurred during policy conversion: "); + return rc; +} From patchwork Fri Jul 17 23:09:31 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Deven Bowers X-Patchwork-Id: 11671413 X-Patchwork-Delegate: snitzer@redhat.com Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 7ED8614E3 for ; Fri, 17 Jul 2020 23:21:10 +0000 (UTC) Received: from us-smtp-1.mimecast.com (us-smtp-delivery-1.mimecast.com [205.139.110.120]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 21CD12064C for ; Fri, 17 Jul 2020 23:21:09 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 21CD12064C Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=linux.microsoft.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=dm-devel-bounces@redhat.com Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-494-20Gh8M9BOQ-g3Wy6qqT5wQ-1; Fri, 17 Jul 2020 19:21:06 -0400 X-MC-Unique: 20Gh8M9BOQ-g3Wy6qqT5wQ-1 Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.phx2.redhat.com [10.5.11.13]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 4ACF78064AA; Fri, 17 Jul 2020 23:21:02 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.21]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 28B7D7B433; Fri, 17 Jul 2020 23:21:02 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id F267896257; Fri, 17 Jul 2020 23:21:01 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.rdu2.redhat.com [10.11.54.4]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id 06HNGneT002304 for ; Fri, 17 Jul 2020 19:16:49 -0400 Received: by smtp.corp.redhat.com (Postfix) id 8534A20A0535; Fri, 17 Jul 2020 23:16:49 +0000 (UTC) Delivered-To: dm-devel@redhat.com Received: from mimecast-mx02.redhat.com (mimecast06.extmail.prod.ext.rdu2.redhat.com [10.11.55.22]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 7C69720A0536 for ; Fri, 17 Jul 2020 23:16:47 +0000 (UTC) Received: from us-smtp-1.mimecast.com (us-smtp-2.mimecast.com [207.211.31.81]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id DC74F1832D22 for ; Fri, 17 Jul 2020 23:16:46 +0000 (UTC) Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by relay.mimecast.com with ESMTP id us-mta-291-6mFFWAKQPsWFV6IGqmIIHA-1; Fri, 17 Jul 2020 19:16:44 -0400 X-MC-Unique: 6mFFWAKQPsWFV6IGqmIIHA-1 Received: from dede-linux-virt.corp.microsoft.com (unknown [131.107.160.54]) by linux.microsoft.com (Postfix) with ESMTPSA id B608720B490C; Fri, 17 Jul 2020 16:09:46 -0700 (PDT) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com B608720B490C From: Deven Bowers To: agk@redhat.com, axboe@kernel.dk, snitzer@redhat.com, jmorris@namei.org, serge@hallyn.com, zohar@linux.ibm.com, viro@zeniv.linux.org.uk, paul@paul-moore.com, eparis@redhat.com, jannh@google.com, dm-devel@redhat.com, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, linux-fsdevel@vger.kernel.org, linux-block@vger.kernel.org, linux-audit@redhat.com Date: Fri, 17 Jul 2020 16:09:31 -0700 Message-Id: <20200717230941.1190744-3-deven.desai@linux.microsoft.com> In-Reply-To: <20200717230941.1190744-1-deven.desai@linux.microsoft.com> References: <20200717230941.1190744-1-deven.desai@linux.microsoft.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.78 on 10.11.54.4 X-MIME-Autoconverted: from quoted-printable to 8bit by lists01.pubmisc.prod.ext.phx2.redhat.com id 06HNGneT002304 X-loop: dm-devel@redhat.com Cc: sashal@kernel.org, mdsakib@microsoft.com, corbet@lwn.net, linux-kernel@vger.kernel.org, pasha.tatshin@soleen.com, nramas@linux.microsoft.com, tyhicks@linux.microsoft.com, jaskarankhurana@linux.microsoft.com Subject: [dm-devel] [RFC PATCH v4 02/12] security: add ipe lsm evaluation loop and audit system X-BeenThere: dm-devel@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: device-mapper development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: dm-devel-bounces@redhat.com Errors-To: dm-devel-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.13 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Add the core logic of the IPE LSM, the evaluation loop (engine), a portion of the audit system, and the skeleton of the policy structure. Signed-off-by: Deven Bowers --- MAINTAINERS | 1 + include/uapi/linux/audit.h | 4 + security/Kconfig | 12 +- security/Makefile | 2 + security/ipe/.gitignore | 2 + security/ipe/Kconfig | 43 ++++++ security/ipe/Makefile | 26 ++++ security/ipe/ipe-audit.c | 231 +++++++++++++++++++++++++++++++ security/ipe/ipe-audit.h | 18 +++ security/ipe/ipe-engine.c | 213 ++++++++++++++++++++++++++++ security/ipe/ipe-engine.h | 37 +++++ security/ipe/ipe-hooks.c | 149 ++++++++++++++++++++ security/ipe/ipe-hooks.h | 61 ++++++++ security/ipe/ipe-policy.h | 62 +++++++++ security/ipe/ipe-prop-internal.h | 37 +++++ security/ipe/ipe-property.c | 142 +++++++++++++++++++ security/ipe/ipe-property.h | 99 +++++++++++++ security/ipe/ipe.c | 70 ++++++++++ security/ipe/ipe.h | 20 +++ 19 files changed, 1223 insertions(+), 6 deletions(-) create mode 100644 security/ipe/.gitignore create mode 100644 security/ipe/Kconfig create mode 100644 security/ipe/Makefile create mode 100644 security/ipe/ipe-audit.c create mode 100644 security/ipe/ipe-audit.h create mode 100644 security/ipe/ipe-engine.c create mode 100644 security/ipe/ipe-engine.h create mode 100644 security/ipe/ipe-hooks.c create mode 100644 security/ipe/ipe-hooks.h create mode 100644 security/ipe/ipe-policy.h create mode 100644 security/ipe/ipe-prop-internal.h create mode 100644 security/ipe/ipe-property.c create mode 100644 security/ipe/ipe-property.h create mode 100644 security/ipe/ipe.c create mode 100644 security/ipe/ipe.h diff --git a/MAINTAINERS b/MAINTAINERS index 5149c130feb0..2876c69435d5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8585,6 +8585,7 @@ M: Deven Bowers L: linux-integrity@vger.kernel.org S: Supported F: scripts/ipe/ +F: security/ipe/ INTEL 810/815 FRAMEBUFFER DRIVER M: Antonino Daplas diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h index 9b6a973f4cc3..5a634cca1d42 100644 --- a/include/uapi/linux/audit.h +++ b/include/uapi/linux/audit.h @@ -154,6 +154,10 @@ #define AUDIT_INTEGRITY_RULE 1805 /* policy rule */ #define AUDIT_INTEGRITY_EVM_XATTR 1806 /* New EVM-covered xattr */ #define AUDIT_INTEGRITY_POLICY_RULE 1807 /* IMA policy rules */ +#define AUDIT_INTEGRITY_POLICY_LOAD 1808 /* IPE Policy Load */ +#define AUDIT_INTEGRITY_POLICY_ACTIVATE 1809 /* IPE Policy Activation */ +#define AUDIT_INTEGRITY_EVENT 1810 /* IPE Evaluation Event */ +#define AUDIT_INTEGRITY_MODE 1811 /* IPE Mode Switch */ #define AUDIT_KERNEL 2000 /* Asynchronous audit record. NOT A REQUEST. */ diff --git a/security/Kconfig b/security/Kconfig index cd3cc7da3a55..94924556b637 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -238,6 +238,7 @@ source "security/loadpin/Kconfig" source "security/yama/Kconfig" source "security/safesetid/Kconfig" source "security/lockdown/Kconfig" +source "security/ipe/Kconfig" source "security/integrity/Kconfig" @@ -277,11 +278,11 @@ endchoice config LSM string "Ordered list of enabled LSMs" - default "lockdown,yama,loadpin,safesetid,integrity,smack,selinux,tomoyo,apparmor,bpf" if DEFAULT_SECURITY_SMACK - default "lockdown,yama,loadpin,safesetid,integrity,apparmor,selinux,smack,tomoyo,bpf" if DEFAULT_SECURITY_APPARMOR - default "lockdown,yama,loadpin,safesetid,integrity,tomoyo,bpf" if DEFAULT_SECURITY_TOMOYO - default "lockdown,yama,loadpin,safesetid,integrity,bpf" if DEFAULT_SECURITY_DAC - default "lockdown,yama,loadpin,safesetid,integrity,selinux,smack,tomoyo,apparmor,bpf" + default "lockdown,yama,loadpin,ipe,safesetid,integrity,smack,selinux,tomoyo,apparmor,bpf" if DEFAULT_SECURITY_SMACK + default "lockdown,yama,loadpin,ipe,safesetid,integrity,apparmor,selinux,smack,tomoyo,bpf" if DEFAULT_SECURITY_APPARMOR + default "lockdown,yama,loadpin,ipe,safesetid,integrity,tomoyo,bpf" if DEFAULT_SECURITY_TOMOYO + default "lockdown,yama,loadpin,ipe,safesetid,integrity,bpf" if DEFAULT_SECURITY_DAC + default "lockdown,yama,loadpin,ipe,safesetid,integrity,selinux,smack,tomoyo,apparmor,bpf" help A comma-separated list of LSMs, in initialization order. Any LSMs left off this list will be ignored. This can be @@ -292,4 +293,3 @@ config LSM source "security/Kconfig.hardening" endmenu - diff --git a/security/Makefile b/security/Makefile index 3baf435de541..48bd063d66e1 100644 --- a/security/Makefile +++ b/security/Makefile @@ -13,6 +13,7 @@ subdir-$(CONFIG_SECURITY_LOADPIN) += loadpin subdir-$(CONFIG_SECURITY_SAFESETID) += safesetid subdir-$(CONFIG_SECURITY_LOCKDOWN_LSM) += lockdown subdir-$(CONFIG_BPF_LSM) += bpf +subdir-$(CONFIG_SECURITY_IPE) += ipe # always enable default capabilities obj-y += commoncap.o @@ -32,6 +33,7 @@ obj-$(CONFIG_SECURITY_SAFESETID) += safesetid/ obj-$(CONFIG_SECURITY_LOCKDOWN_LSM) += lockdown/ obj-$(CONFIG_CGROUPS) += device_cgroup.o obj-$(CONFIG_BPF_LSM) += bpf/ +obj-$(CONFIG_SECURITY_IPE) += ipe/ # Object integrity file lists subdir-$(CONFIG_INTEGRITY) += integrity diff --git a/security/ipe/.gitignore b/security/ipe/.gitignore new file mode 100644 index 000000000000..bbf824e665d7 --- /dev/null +++ b/security/ipe/.gitignore @@ -0,0 +1,2 @@ +# Generated Boot Policy +ipe-bp.c diff --git a/security/ipe/Kconfig b/security/ipe/Kconfig new file mode 100644 index 000000000000..f9b8292a88ed --- /dev/null +++ b/security/ipe/Kconfig @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Integrity Policy Enforcement (IPE) configuration +# + +menuconfig SECURITY_IPE + bool "Integrity Policy Enforcement (IPE)" + depends on SECURITY && AUDIT + select SYSTEM_DATA_VERIFICATION + help + This option enables the Integrity Policy Enforcement subsystem + allowing systems to enforce integrity having no dependencies + on filesystem metadata, making its decisions based off of kernel- + resident features and data structures. A key feature of IPE is a + customizable policy to allow admins to reconfigure integrity + requirements on the fly. + + If unsure, answer N. + +if SECURITY_IPE + +config SECURITY_IPE_BOOT_POLICY + string "Integrity policy to apply on system startup" + help + This option specifies a filepath to a IPE policy that is compiled + into the kernel. This policy will be enforced until a policy update + is deployed via the "ipe.policy" sysctl. + + If unsure, leave blank. + +config SECURITY_IPE_PERMISSIVE_SWITCH + bool "Enable the ability to switch IPE to permissive mode" + default y + help + This option enables two ways of switching IPE to permissive mode, + a sysctl (if enabled), `ipe.enforce`, or a kernel command line + parameter, `ipe.enforce`. If either of these are set to 0, files + will be subject to IPE's policy, audit messages will be logged, but + the policy will not be enforced. + + If unsure, answer Y. + +endif diff --git a/security/ipe/Makefile b/security/ipe/Makefile new file mode 100644 index 000000000000..078acd026ba9 --- /dev/null +++ b/security/ipe/Makefile @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (C) Microsoft Corporation. All rights reserved. +# +# Makefile for building the IPE module as part of the kernel tree. +# + +quiet_cmd_polgen = IPE_POL $(patsubst "%",%,$(2)) + cmd_polgen = scripts/ipe/polgen/polgen security/ipe/ipe-bp.c $(2) + +$(eval $(call config_filename,SECURITY_IPE_BOOT_POLICY)) + +targets += ipe-bp.c +$(obj)/ipe-bp.c: scripts/ipe/polgen/polgen $(SECURITY_IPE_BOOT_POLICY_FILENAME) FORCE + $(call if_changed,polgen,$(SECURITY_IPE_BOOT_POLICY_FILENAME)) + +obj-$(CONFIG_SECURITY_IPE) += \ + ipe.o \ + ipe-audit.o \ + ipe-bp.o \ + ipe-engine.o \ + ipe-property.o \ + ipe-hooks.o \ + ipe-secfs.o \ + +clean-files := ipe-bp.c diff --git a/security/ipe/ipe-audit.c b/security/ipe/ipe-audit.c new file mode 100644 index 000000000000..230884e91d03 --- /dev/null +++ b/security/ipe/ipe-audit.c @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ + +#include "ipe.h" +#include "ipe-audit.h" +#include "ipe-engine.h" +#include "ipe-prop-internal.h" + +#include +#include +#include +#include +#include +#include +#include + +#define ACTION_STR(a) ((a) == ipe_action_allow ? "ALLOW" : "DENY") + +#define IPE_UNKNOWN "UNKNOWN" + +/* Keep in sync with ipe_op in ipe-hooks.h */ +const char *audit_op_names[] = { + IPE_OP_EXECUTE, + IPE_OP_FIRMWARE, + IPE_OP_KEXEC_IMAGE, + IPE_OP_KEXEC_INITRAMFS, + IPE_OP_X509_CERTIFICATE, + IPE_OP_POLICY, + IPE_OP_KMODULE, + IPE_OP_KERNEL_READ, + IPE_UNKNOWN, +}; + +/* Keep in sync with ipe_hook in ipe-hooks.h */ +const char *audit_hook_names[] = { + IPE_HOOK_EXEC, + IPE_HOOK_MMAP, + IPE_HOOK_MPROTECT, + IPE_HOOK_KERNEL_READ, + IPE_HOOK_KERNEL_LOAD, + IPE_UNKNOWN, +}; + +/** + * ipe_audit_mode: Emit an audit event indicating what mode IPE is currently + * in. + * + * This event is of form "IPE mode=(enforce|audit)" + */ +void ipe_audit_mode(void) +{ + struct audit_buffer *ab; + const char *mode_str = (ipe_enforce) ? IPE_MODE_ENFORCE : + IPE_MODE_PERMISSIVE; + + ab = audit_log_start(audit_context(), GFP_KERNEL, + AUDIT_INTEGRITY_MODE); + if (!ab) + return; + + audit_log_format(ab, "IPE mode=%s", mode_str); + + audit_log_end(ab); +} + +/** + * audit_engine_ctx: Add the string representation of ipe_engine_ctx to the + * end of an audit buffer. + * @ab: the audit buffer to append the string representation of @ctx + * @ctx: the ipe_engine_ctx structure to transform into a string + * representation + * + * This string representation is of form: + * "ctx_pid=%d ctx_op=%s ctx_hook=%s ctx_comm=%s ctx_audit_pathname=%s ctx_ino=%ld ctx_dev=%s" + * + * Certain fields may be omitted or replaced with ERR(%d). + * + */ +static void audit_engine_ctx(struct audit_buffer *ab, + const struct ipe_engine_ctx *ctx) +{ + audit_log_format(ab, "ctx_pid=%d ctx_op=%s ctx_hook=%s ctx_comm=", + task_tgid_nr(current), + audit_op_names[ctx->op], + audit_hook_names[ctx->hook]); + + audit_log_untrustedstring(ab, current->comm); + + if (ctx->file) { + if (IS_ERR(ctx->audit_pathname)) { + audit_log_format(ab, " ctx_audit_pathname=ERR(%ld) ", + PTR_ERR(ctx->audit_pathname)); + } else { + audit_log_format(ab, " ctx_audit_pathname=\"%s\" ", + ctx->audit_pathname); + } + + audit_log_format(ab, "ctx_ino=%ld ctx_dev=%s", + ctx->file->f_inode->i_ino, + ctx->file->f_inode->i_sb->s_id); + } +} + +struct prop_audit_ctx { + struct audit_buffer *ab; + const struct ipe_engine_ctx *ctx; +}; + +/** + * audit_property: callback to print a property, used with ipe_for_each_prop. + * @prop: property to print an audit record for. + * @ctx: context passed to ipe_for_each_prop. In this case, it is of type + * prop_audit_ctx, containing the audit buffer and engine ctx. + * + * Return: + * 0 - Always + */ +static int audit_property(const struct ipe_property *prop, void *ctx) +{ + const struct prop_audit_ctx *aud_ctx = (struct prop_audit_ctx *)ctx; + + audit_log_format(aud_ctx->ab, "prop_%s=", prop->property_name); + prop->ctx_audit(aud_ctx->ab, aud_ctx->ctx); + audit_log_format(aud_ctx->ab, " "); + + return 0; +} + +/** + * audit_eval_properties: Append the string representation of evaluated + * properties to an audit buffer. + * @ab: the audit buffer to append the string representation of the evaluated + * properties. + * @ctx: the ipe_engine_ctx structure to pass to property audit function. + * + * This string representation is of form: + * "prop_key1=value1 prop_key2=value2 ... " + * + * Certain values may be replaced with ERR(%d). Prop may also be empty, + * and thus omitted entirely. + * + */ +static inline void audit_eval_properties(struct audit_buffer *ab, + const struct ipe_engine_ctx *ctx) +{ + const struct prop_audit_ctx aud_ctx = { + .ab = ab, + .ctx = ctx + }; + + (void)ipe_for_each_prop(audit_property, (void *)&aud_ctx); +} + +/** + * audit_rule: Add the string representation of a non-default IPE rule to the + * end of an audit buffer. + * @ab: the audit buffer to append the string representation of a rule. + * @rule: the ipe_rule structure to transform into a string representation. + * + * This string representation is of form: + * "rule={op=%s key1=value1 key2=value2 ... action=%s}" + * + * Certain values may be replaced with ERR(%d). + * + */ +static void audit_rule(struct audit_buffer *ab, + const struct ipe_rule *rule) +{ + struct ipe_prop_container *ptr; + + audit_log_format(ab, "rule=\"op=%s ", audit_op_names[rule->op]); + + list_for_each_entry(ptr, &rule->props, next) { + audit_log_format(ab, "%s=", ptr->prop->property_name); + + ptr->prop->rule_audit(ab, ptr->value); + + audit_log_format(ab, " "); + } + + audit_log_format(ab, "action=%s\"", ACTION_STR(rule->action)); +} + +/** + * ipe_audit_match: Emit an audit event indicating that the IPE engine has + * determined a match to a rule in IPE policy. + * @ctx: the engine context structure to audit + * @rule: The rule that was matched. If NULL, then assumed to be a default + * either operation specific, indicated by table, or global. + * @table: the operation-specific rule table. If NULL, then it assumed + * that the global default is matched. + * @match_type: The type of match that the engine used during evaluation + * @action: The action that the engine decided to take + * @rule: The rule that was matched. Must be set if @match_type is + * ipe_match_rule and NULL otherwise. + */ +void ipe_audit_match(const struct ipe_engine_ctx *ctx, + enum ipe_match match_type, enum ipe_action action, + const struct ipe_rule *rule) +{ + struct audit_buffer *ab; + + if (!ipe_success_audit && action == ipe_action_allow) + return; + + ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN, + AUDIT_INTEGRITY_EVENT); + if (!ab) + return; + + audit_log_format(ab, "IPE "); + + audit_engine_ctx(ab, ctx); + + audit_log_format(ab, " "); + + audit_eval_properties(ab, ctx); + + if (match_type == ipe_match_rule) + audit_rule(ab, rule); + else if (match_type == ipe_match_table) + audit_log_format(ab, "rule=\"DEFAULT op=%s action=%s\"", + audit_op_names[ctx->op], ACTION_STR(action)); + else if (match_type == ipe_match_global) + audit_log_format(ab, "rule=\"DEFAULT action=%s\"", + ACTION_STR(action)); + + audit_log_end(ab); +} diff --git a/security/ipe/ipe-audit.h b/security/ipe/ipe-audit.h new file mode 100644 index 000000000000..c4fca5e2ab73 --- /dev/null +++ b/security/ipe/ipe-audit.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ + +#include "ipe-engine.h" +#include "ipe-policy.h" + +#ifndef IPE_AUDIT_H +#define IPE_AUDIT_H + +void ipe_audit_mode(void); + +void ipe_audit_match(const struct ipe_engine_ctx *ctx, + enum ipe_match match_type, enum ipe_action action, + const struct ipe_rule *rule); + +#endif /* IPE_AUDIT_H */ diff --git a/security/ipe/ipe-engine.c b/security/ipe/ipe-engine.c new file mode 100644 index 000000000000..c79f422245c6 --- /dev/null +++ b/security/ipe/ipe-engine.c @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ + +#include "ipe.h" +#include "ipe-property.h" +#include "ipe-prop-internal.h" +#include "ipe-policy.h" +#include "ipe-engine.h" +#include "ipe-audit.h" + +#include +#include +#include +#include +#include +#include + +const struct ipe_policy *ipe_active_policy; + +/** + * get_audit_pathname: Return the absolute path of the file struct passed in + * @file: file to derive an absolute path from. + * + * This function walks past chroots and mount points. + * + * Return: + * !NULL - OK + * ERR_PTR(-ENOENT) - No File + * ERR_PTR(-ENOMEM) - No Memory + * ERR_PTR(-ENAMETOOLONG) - Path Exceeds PATH_MAX + */ +static char *get_audit_pathname(const struct file *file) +{ + int rc = 0; + char *pos = NULL; + char *pathbuf = NULL; + struct super_block *sb; + char *temp_path = NULL; + + /* No File to get Path From */ + if (!file) + return ERR_PTR(-ENOENT); + + sb = file->f_path.dentry->d_sb; + + pathbuf = __getname(); + if (!pathbuf) + return ERR_PTR(-ENOMEM); + + pos = d_absolute_path(&file->f_path, pathbuf, PATH_MAX); + if (IS_ERR(pos)) { + rc = PTR_ERR(pos); + goto err; + } + + temp_path = __getname(); + if (!temp_path) { + rc = -ENOMEM; + goto err; + } + + strlcpy(temp_path, pos, PATH_MAX); + + __putname(pathbuf); + + return temp_path; + +err: + __putname(pathbuf); + return ERR_PTR(rc); +} + +/** + * free_ctx: free a previously allocated ipe_engine_ctx struct + * @ctx: structure to allocate. + * + * The caller is required to free @ctx, if previously allocated. + */ +static void free_ctx(struct ipe_engine_ctx *ctx) +{ + if (IS_ERR_OR_NULL(ctx)) + return; + + if (!IS_ERR_OR_NULL(ctx->audit_pathname)) + __putname(ctx->audit_pathname); + + kfree(ctx); +} + +/** + * build_ctx: allocate a new ipe_engine_ctx structure + * @file: File that is being evaluated against IPE policy. + * @op: Operation that the file is being evaluated against. + * @hook: Specific hook that the file is being evaluated through. + * + * Return: + * !NULL - OK + * ERR_PTR(-ENOMEM) - no memory + */ +static struct ipe_engine_ctx *build_ctx(const struct file *file, + enum ipe_op op, enum ipe_hook hook) +{ + struct ipe_engine_ctx *local; + + local = kzalloc(sizeof(*local), GFP_KERNEL); + if (!local) + return ERR_PTR(-ENOMEM); + + /* if there's an error here, it's O.K. */ + local->audit_pathname = get_audit_pathname(file); + local->file = file; + local->op = op; + local->hook = hook; + + return local; +} + +/** + * evaluate: Process an @ctx against IPE's current active policy. + * @ctx: the engine ctx to perform an evaluation on. + * @cache: the red-black tree root that is used for cache storage. + * + * This uses a preallocated @cache as storage for the properties to avoid + * re-evaulation. + * + * Return: + * -EACCES - A match occurred against a "action=DENY" rule + * -ENOMEM - Out of memory + */ +static int evaluate(struct ipe_engine_ctx *ctx) +{ + int rc = 0; + bool match = false; + enum ipe_action action; + enum ipe_match match_type; + const struct ipe_rule *rule; + const struct ipe_policy *pol; + const struct ipe_rule_table *rules; + const struct ipe_prop_container *prop; + + if (!rcu_access_pointer(ipe_active_policy)) + return rc; + + rcu_read_lock(); + + pol = rcu_dereference(ipe_active_policy); + + rules = &pol->ops[ctx->op]; + + list_for_each_entry(rule, &rules->rules, next) { + match = true; + + list_for_each_entry(prop, &rule->props, next) + match = match && prop->prop->eval(ctx, prop->value); + + if (match) + break; + } + + if (match) { + match_type = ipe_match_rule; + action = rule->action; + } else if (rules->def != ipe_action_unset) { + match_type = ipe_match_table; + action = rules->def; + rule = NULL; + } else { + match_type = ipe_match_global; + action = pol->def; + rule = NULL; + } + + ipe_audit_match(ctx, match_type, action, rule); + + if (action == ipe_action_deny) + rc = -EACCES; + + if (ipe_enforce == 0) + rc = 0; + + rcu_read_unlock(); + return rc; +} + +/** + * ipe_process_event: Perform an evaluation of @file, @op, and @hook against + * IPE's current active policy. + * @file: File that is being evaluated against IPE policy. + * @op: Operation that the file is being evaluated against. + * @hook: Specific hook that the file is being evaluated through. + * + * Return: + * -ENOMEM: (No Memory) + * -EACCES: (A match occurred against a "action=DENY" rule) + */ +int ipe_process_event(const struct file *file, enum ipe_op op, + enum ipe_hook hook) +{ + int rc = 0; + struct ipe_engine_ctx *ctx; + + ctx = build_ctx(file, op, hook); + if (IS_ERR(ctx)) + goto cleanup; + + rc = evaluate(ctx); + +cleanup: + free_ctx(ctx); + return rc; +} diff --git a/security/ipe/ipe-engine.h b/security/ipe/ipe-engine.h new file mode 100644 index 000000000000..d9a95674e70d --- /dev/null +++ b/security/ipe/ipe-engine.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ + +#include "ipe-hooks.h" + +#include +#include +#include + +#ifndef IPE_ENGINE_H +#define IPE_ENGINE_H + +struct ipe_engine_ctx { + enum ipe_op op; + enum ipe_hook hook; + const struct file *file; + const char *audit_pathname; +}; + +struct ipe_prop_cache { + struct rb_node node; + void *storage; + const struct ipe_property *prop; +}; + +enum ipe_match { + ipe_match_rule = 0, + ipe_match_table, + ipe_match_global +}; + +int ipe_process_event(const struct file *file, enum ipe_op op, + enum ipe_hook hook); + +#endif /* IPE_ENGINE_H */ diff --git a/security/ipe/ipe-hooks.c b/security/ipe/ipe-hooks.c new file mode 100644 index 000000000000..071c4af23a3d --- /dev/null +++ b/security/ipe/ipe-hooks.c @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ + +#include "ipe.h" +#include "ipe-hooks.h" +#include "ipe-engine.h" + +#include +#include +#include +#include +#include +#include +#include + +/** + * ipe_on_exec: LSM hook called on the exec family of system calls. + * @bprm: A structure to hold arguments that are used when loading binaries, + * used to extract the file being executed. + * + * Return: + * 0 - OK + * !0 - see ipe_process_event + */ +int ipe_on_exec(struct linux_binprm *bprm) +{ + return ipe_process_event(bprm->file, ipe_op_execute, ipe_hook_exec); +} + +/** + * ipe_on_mmap: LSM hook called on the mmap system call. + * @file: File being mapped into memory. + * @reqprot: Unused. + * @prot: A protection mapping of the memory region, calculated based on + * @reqprot, and the system configuration. + * @flags: Unused. + * + * Return: + * 0 - OK + * !0 - see ipe_process_event + */ +int ipe_on_mmap(struct file *file, unsigned long reqprot, unsigned long prot, + unsigned long flags) +{ + if (prot & PROT_EXEC) + return ipe_process_event(file, ipe_op_execute, ipe_hook_mmap); + + return 0; +} + +/** + * ipe_on_mprotect: LSM hook called on the mprotect system call + * @vma: A structure representing the existing memory region. + * @reqprot: Unused. + * @prot: A protection mapping of the memory region, calculated based on + * @reqprot, and the system configuration. + * + * Return: + * 0 - OK + * !0 - see ipe_process_event + */ +int ipe_on_mprotect(struct vm_area_struct *vma, unsigned long reqprot, + unsigned long prot) +{ + if ((prot & PROT_EXEC) && !(vma->vm_flags & VM_EXEC)) + return ipe_process_event(vma->vm_file, ipe_op_execute, + ipe_hook_mprotect); + + return 0; +} + +/** + * ipe_on_kernel_read: LSM hook called on kernel_read_file. + * @file: File being read by the hook kernel_read_file. + * @id: Enumeration indicating the type of file being read. + * + * For more information, see the LSM hook, kernel_read_file. + * + * Return: + * 0 - OK + * !0 - see ipe_process_event + */ +int ipe_on_kernel_read(struct file *file, enum kernel_read_file_id id) +{ + switch (id) { + case READING_FIRMWARE: + case READING_FIRMWARE_PREALLOC_BUFFER: + return ipe_process_event(file, ipe_op_firmware, + ipe_hook_kernel_read); + case READING_MODULE: + return ipe_process_event(file, ipe_op_kmodule, + ipe_hook_kernel_read); + case READING_KEXEC_INITRAMFS: + return ipe_process_event(file, ipe_op_kexec_initramfs, + ipe_hook_kernel_read); + case READING_KEXEC_IMAGE: + return ipe_process_event(file, ipe_op_kexec_image, + ipe_hook_kernel_read); + case READING_POLICY: + return ipe_process_event(file, ipe_op_policy, + ipe_hook_kernel_read); + case READING_X509_CERTIFICATE: + return ipe_process_event(file, ipe_op_x509, + ipe_hook_kernel_read); + default: + return ipe_process_event(file, ipe_op_kernel_read, + ipe_hook_kernel_read); + } +} + +/** + * ipe_on_kernel_load_data: LSM hook called on kernel_load_data. + * @id: Enumeration indicating what type of data is being loaded. + * + * For more information, see the LSM hook, kernel_load_data. + * + * Return: + * 0 - OK + * !0 - see ipe_process_event + */ +int ipe_on_kernel_load_data(enum kernel_load_data_id id) +{ + switch (id) { + case LOADING_FIRMWARE: + case LOADING_FIRMWARE_PREALLOC_BUFFER: + return ipe_process_event(NULL, ipe_op_firmware, + ipe_hook_kernel_load); + case LOADING_MODULE: + return ipe_process_event(NULL, ipe_op_kmodule, + ipe_hook_kernel_load); + case LOADING_KEXEC_INITRAMFS: + return ipe_process_event(NULL, ipe_op_kexec_initramfs, + ipe_hook_kernel_load); + case LOADING_KEXEC_IMAGE: + return ipe_process_event(NULL, ipe_op_kexec_image, + ipe_hook_kernel_load); + case LOADING_POLICY: + return ipe_process_event(NULL, ipe_op_policy, + ipe_hook_kernel_load); + case LOADING_X509_CERTIFICATE: + return ipe_process_event(NULL, ipe_op_x509, + ipe_hook_kernel_load); + default: + return ipe_process_event(NULL, ipe_op_kernel_read, + ipe_hook_kernel_load); + } +} diff --git a/security/ipe/ipe-hooks.h b/security/ipe/ipe-hooks.h new file mode 100644 index 000000000000..806659b7cdbe --- /dev/null +++ b/security/ipe/ipe-hooks.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include + +#ifndef IPE_HOOK_H +#define IPE_HOOK_H + +#define IPE_HOOK_EXEC "EXEC" +#define IPE_HOOK_MMAP "MMAP" +#define IPE_HOOK_MPROTECT "MPROTECT" +#define IPE_HOOK_KERNEL_READ "KERNEL_READ" +#define IPE_HOOK_KERNEL_LOAD "KERNEL_LOAD" + +enum ipe_hook { + ipe_hook_exec = 0, + ipe_hook_mmap, + ipe_hook_mprotect, + ipe_hook_kernel_read, + ipe_hook_kernel_load, + ipe_hook_max +}; + +/* + * The sequence between ipe_op_firmware and ipe_op_kmodule + * must remain the same for ipe_op_kernel read to function + * appropriately. + */ +enum ipe_op { + ipe_op_execute = 0, + ipe_op_firmware, + ipe_op_kexec_image, + ipe_op_kexec_initramfs, + ipe_op_x509, + ipe_op_policy, + ipe_op_kmodule, + ipe_op_kernel_read, + ipe_op_max +}; + +int ipe_on_exec(struct linux_binprm *bprm); + +int ipe_on_mmap(struct file *file, unsigned long reqprot, unsigned long prot, + unsigned long flags); + +int ipe_on_mprotect(struct vm_area_struct *vma, unsigned long reqprot, + unsigned long prot); + +int ipe_on_kernel_read(struct file *file, enum kernel_read_file_id id); + +int ipe_on_kernel_load_data(enum kernel_load_data_id id); + +#endif /* IPE_HOOK_H */ diff --git a/security/ipe/ipe-policy.h b/security/ipe/ipe-policy.h new file mode 100644 index 000000000000..c0c9f2962c92 --- /dev/null +++ b/security/ipe/ipe-policy.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ + +#include "ipe-hooks.h" +#include "ipe-property.h" + +#include +#include +#include +#include + +#ifndef IPE_POLICY_H +#define IPE_POLICY_H + +#define IPE_HEADER_POLICY_NAME "policy_name" +#define IPE_HEADER_POLICY_VERSION "policy_version" + +extern const char *const ipe_boot_policy; +extern const struct ipe_policy *ipe_active_policy; + +enum ipe_action { + ipe_action_unset = 0, + ipe_action_allow, + ipe_action_deny +}; + +struct ipe_prop_container { + struct list_head next; + void *value; + const struct ipe_property *prop; +}; + +struct ipe_rule { + struct list_head props; + struct list_head next; + enum ipe_action action; + enum ipe_op op; +}; + +struct ipe_rule_table { + struct list_head rules; + enum ipe_action def; +}; + +struct ipe_pol_ver { + u16 major; + u16 minor; + u16 rev; +}; + +struct ipe_policy { + char *policy_name; + struct ipe_pol_ver policy_version; + enum ipe_action def; + + /* KERNEL_READ stores no data itself */ + struct ipe_rule_table ops[ipe_op_max - 1]; +}; + +#endif /* IPE_POLICY_H */ diff --git a/security/ipe/ipe-prop-internal.h b/security/ipe/ipe-prop-internal.h new file mode 100644 index 000000000000..95a2081e77ee --- /dev/null +++ b/security/ipe/ipe-prop-internal.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ + +#include "ipe-property.h" + +#include + +#ifndef IPE_PROPERTY_INTERNAL_H +#define IPE_PROPERTY_INTERNAL_H + +#define IPE_PROPERTY_OPERATION "op" +#define IPE_PROPERTY_DEFAULT "DEFAULT" +#define IPE_PROPERTY_ACTION "action" + +#define IPE_OP_EXECUTE "EXECUTE" +#define IPE_OP_FIRMWARE "FIRMWARE" +#define IPE_OP_KEXEC_IMAGE "KEXEC_IMAGE" +#define IPE_OP_KEXEC_INITRAMFS "KEXEC_INITRAMFS" +#define IPE_OP_X509_CERTIFICATE "X509_CERT" +#define IPE_OP_POLICY "POLICY" +#define IPE_OP_KMODULE "KMODULE" +#define IPE_OP_KERNEL_READ "KERNEL_READ" + +struct ipe_prop_reg { + struct rb_node node; + const struct ipe_property *prop; +}; + +int ipe_for_each_prop(int (*view)(const struct ipe_property *prop, + void *ctx), + void *ctx); + +const struct ipe_property *ipe_lookup_prop(const char *key); + +#endif /* IPE_PROPERTY_INTERNAL_H */ diff --git a/security/ipe/ipe-property.c b/security/ipe/ipe-property.c new file mode 100644 index 000000000000..d4b0283f86bd --- /dev/null +++ b/security/ipe/ipe-property.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ + +#include "ipe.h" +#include "ipe-prop-internal.h" +#include "ipe-property.h" + +#include +#include +#include + +/* global root containing all registered properties */ +struct rb_root ipe_registry_root = RB_ROOT; + +/** + * reg_lookup: Attempt to find a `prop_reg` structure with property_name @key. + * @key: The property_name to look for in the tree. + * + * Return: + * ipe_prop_reg structure - OK + * NULL - No such property exists + */ +static struct ipe_prop_reg *reg_lookup(const char *key) +{ + struct rb_node *n = ipe_registry_root.rb_node; + + while (n) { + int r; + struct ipe_prop_reg *reg = + container_of(n, struct ipe_prop_reg, node); + + r = strcmp(reg->prop->property_name, key); + if (r == 0) + return reg; + else if (r > 0) + n = n->rb_right; + else + n = n->rb_left; + } + + return NULL; +} + +/** + * ipe_lookup_prop: Attempt to find a ipe_property structure by name @key. + * @key: The property_name to look for in the tree. + * + * Return: + * ipe_property structure - OK + * NULL - No property exists under @key + */ +const struct ipe_property *ipe_lookup_prop(const char *key) +{ + struct ipe_prop_reg *reg = reg_lookup(key); + + if (!reg) + return NULL; + + return reg->prop; +} + +/** + * ipe_register_property: Insert a property into the registration system. + * @prop: Read-only property structure containing the property_name, as well + * as the necessary function pointers for a property. + * + * The caller needs to maintain the lifetime of @prop throughout the life of + * the system, after calling ipe_register_property. + * + * All necessary properties need to be loaded via this method before + * loading a policy, otherwise the properties will be ignored as unknown. + * + * Return: + * 0 - OK + * -EEXIST - A key exists with the name @prop->property_name + * -ENOMEM - Out of Memory + */ +int ipe_register_property(const struct ipe_property *prop) +{ + struct rb_node *parent = NULL; + struct ipe_prop_reg *new_data = NULL; + struct rb_node **new = &ipe_registry_root.rb_node; + + while (*new) { + int r; + struct ipe_prop_reg *reg = + container_of(*new, struct ipe_prop_reg, node); + + parent = *new; + + r = strcmp(reg->prop->property_name, prop->property_name); + if (r == 0) + return -EEXIST; + else if (r > 0) + new = &((*new)->rb_right); + else + new = &((*new)->rb_left); + } + + new_data = kzalloc(sizeof(*new_data), GFP_KERNEL); + if (!new_data) + return -ENOMEM; + + new_data->prop = prop; + + rb_link_node(&new_data->node, parent, new); + rb_insert_color(&new_data->node, &ipe_registry_root); + + return 0; +} + +/** + * ipe_for_each_prop: Iterate over all currently-registered properties + * calling @fn on the values, and providing @view @ctx. + * @view: The function to call for each property. This is given the property + * structure as the first argument, and @ctx as the second. + * @ctx: caller-specified context that is passed to the function. Can be NULL. + * + * Return: + * 0 - OK + * !0 - Proper errno as returned by @view. + */ +int ipe_for_each_prop(int (*view)(const struct ipe_property *prop, + void *ctx), + void *ctx) +{ + struct rb_node *node; + struct ipe_prop_reg *val; + int rc = 0; + + for (node = rb_first(&ipe_registry_root); node; node = rb_next(node)) { + val = container_of(node, struct ipe_prop_reg, node); + + rc = view(val->prop, ctx); + if (rc) + return rc; + } + + return rc; +} diff --git a/security/ipe/ipe-property.h b/security/ipe/ipe-property.h new file mode 100644 index 000000000000..cf570d52d0d2 --- /dev/null +++ b/security/ipe/ipe-property.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ + +#include "ipe-engine.h" + +#include +#include + +#ifndef IPE_PROPERTY_H +#define IPE_PROPERTY_H + +/** + * ipe_property_evaluator: Determines whether a file subject matches the + * property. + * @value: Value to compare against for a match + * + * NOTE: This is done in an rcu read critical section - sleeping + * allocations are prohibited. + * + * Return: + * true - The property matches evaluation + * false - The property does not match evaluation + */ +typedef bool (*ipe_property_evaluator)(const struct ipe_engine_ctx *ctx, + const void *value); + +/** + * ipe_property_audit: Transform a rule value into a string representation. + * @ab: Audit buffer to add the string representation of @value to. + * @value: Value to transform into a string representation. + * + * NOTE: This is done in an rcu read critical section - sleeping + * allocations are prohibited. + */ +typedef void (*ipe_property_audit)(struct audit_buffer *ab, const void *value); + +/** + * ipe_ctx_audit: Called by the auditing to provide the values + * that were evaluated about the subject, @ctx->file, to determine how + * a value was evaluated. + * + * NOTE: This is done in an rcu read critical section - sleeping + * allocations are prohibited. + * + * @ab: Audit buffer to add the string representation of @value to. + * @value: Value to transform into a string representation. + * + */ +typedef void (*ipe_ctx_audit)(struct audit_buffer *ab, + const struct ipe_engine_ctx *ctx); + +/** + * ipe_parse_value: Transform a string representation of a rule into an + * internal ipe data-structure, opaque to the engine. + * @val_str: String-value parsed by the policy parser. + * @value: Valid-pointer indicating address to store parsed value. + * + * Return: + * 0 - OK + * !0 - ERR, use Standard Return Codes + */ +typedef int(*ipe_parse_value)(const char *val_str, void **value); + +/** + * ipe_dup_val: Called by the policy parser to make duplicate properties for + * pseudo-properties like "KERNEL_READ". + * @src: Value to copy. + * @dest: Pointer to the destination where the value should be copied. + * + * Return: + * 0 - OK + * !0 - ERR, use Standard Return Codes + */ +typedef int (*ipe_dup_val)(const void *src, void **dest); + +/** + * ipe_free_value: Free a policy value, created by ipe_parse_value. + * @value: Valid-pointer to the value to be interpreted and + * freed by the property. + * + * Optional, can be NULL - in which case, this will not be called. + */ +typedef void (*ipe_free_value)(void **value); + +struct ipe_property { + const char *const property_name; + ipe_property_evaluator eval; + ipe_property_audit rule_audit; + ipe_ctx_audit ctx_audit; + ipe_parse_value parse; + ipe_dup_val dup; + ipe_free_value free_val; +}; + +int ipe_register_property(const struct ipe_property *prop); + +#endif /* IPE_PROPERTY_H */ diff --git a/security/ipe/ipe.c b/security/ipe/ipe.c new file mode 100644 index 000000000000..a339bca64dd9 --- /dev/null +++ b/security/ipe/ipe.c @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ + +#include "ipe.h" +#include "ipe-policy.h" +#include "ipe-hooks.h" +#include "ipe-secfs.h" + +#include +#include +#include +#include +#include +#include +#include + +static struct security_hook_list ipe_hooks[] __lsm_ro_after_init = { + LSM_HOOK_INIT(bprm_check_security, ipe_on_exec), + LSM_HOOK_INIT(mmap_file, ipe_on_mmap), + LSM_HOOK_INIT(kernel_read_file, ipe_on_kernel_read), + LSM_HOOK_INIT(kernel_load_data, ipe_on_kernel_load_data), + LSM_HOOK_INIT(file_mprotect, ipe_on_mprotect), +}; + +/** + * ipe_init: Entry point of IPE. + * + * This is called at LSM init, which happens occurs early during kernel + * start up. During this phase, IPE initializes the sysctls, loads the + * properties compiled into the kernel, and register's IPE's hooks. + * The boot policy is loaded later, during securityfs init, at which point + * IPE will start enforcing its policy. + * + * Return: + * 0 - OK + * -ENOMEM - sysctl registration failed. + */ +static int __init ipe_init(void) +{ + int rc; + + pr_info("mode=%s", (ipe_enforce == 1) ? IPE_MODE_ENFORCE : + IPE_MODE_PERMISSIVE); + + security_add_hooks(ipe_hooks, ARRAY_SIZE(ipe_hooks), "IPE"); + + return rc; +} + +DEFINE_LSM(ipe) = { + .name = "ipe", + .init = ipe_init, +}; + +bool ipe_enforce = true; +bool ipe_success_audit; + +#ifdef SECURITY_IPE_PERMISSIVE_SWITCH + +/* Module Parameter for Default Behavior on Boot */ +module_param_named(enforce, ipe_enforce, bool, 0644); +MODULE_PARM_DESC(ipe_enforce, "IPE Permissive Switch"); + +#endif /* SECURITY_IPE_PERMISSIVE_SWITCH */ + +/* Module Parameter for Success Audit on Boot */ +module_param_named(success_audit, ipe_success_audit, bool, 0644); +MODULE_PARM_DESC(ipe_success_audit, "IPE Audit on Success"); diff --git a/security/ipe/ipe.h b/security/ipe/ipe.h new file mode 100644 index 000000000000..af72bb574f73 --- /dev/null +++ b/security/ipe/ipe.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ + +#ifndef IPE_H +#define IPE_H + +#define pr_fmt(fmt) "IPE " fmt "\n" + +#include +#include + +#define IPE_MODE_ENFORCE "enforce" +#define IPE_MODE_PERMISSIVE "permissive" + +extern bool ipe_enforce; +extern bool ipe_success_audit; + +#endif /* IPE_H */ From patchwork Fri Jul 17 23:09:32 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Deven Bowers X-Patchwork-Id: 11671421 X-Patchwork-Delegate: snitzer@redhat.com Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 45FBD14E3 for ; Fri, 17 Jul 2020 23:21:32 +0000 (UTC) Received: from us-smtp-1.mimecast.com (us-smtp-delivery-1.mimecast.com [207.211.31.120]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id D7B0A2064C for ; Fri, 17 Jul 2020 23:21:31 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org D7B0A2064C Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=linux.microsoft.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=dm-devel-bounces@redhat.com Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-259-gJ4QGirOOjmkMqa56fuSRw-1; Fri, 17 Jul 2020 19:21:28 -0400 X-MC-Unique: gJ4QGirOOjmkMqa56fuSRw-1 Received: from smtp.corp.redhat.com (int-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.11]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 8B70B801E6A; Fri, 17 Jul 2020 23:21:24 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.21]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 6EBC872E48; Fri, 17 Jul 2020 23:21:24 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id 49B7B96257; Fri, 17 Jul 2020 23:21:24 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.rdu2.redhat.com [10.11.54.3]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id 06HNGmVW002236 for ; Fri, 17 Jul 2020 19:16:49 -0400 Received: by smtp.corp.redhat.com (Postfix) id DAADC105458C; Fri, 17 Jul 2020 23:16:48 +0000 (UTC) Delivered-To: dm-devel@redhat.com Received: from mimecast-mx02.redhat.com (mimecast05.extmail.prod.ext.rdu2.redhat.com [10.11.55.21]) by smtp.corp.redhat.com (Postfix) with ESMTPS id D38FC10545B4 for ; Fri, 17 Jul 2020 23:16:48 +0000 (UTC) Received: from us-smtp-1.mimecast.com (us-smtp-2.mimecast.com [205.139.110.61]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 6C99883B7F9 for ; Fri, 17 Jul 2020 23:16:48 +0000 (UTC) Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by relay.mimecast.com with ESMTP id us-mta-2-O0s-hUgiN7GctoldkfHGaw-1; Fri, 17 Jul 2020 19:16:44 -0400 X-MC-Unique: O0s-hUgiN7GctoldkfHGaw-1 Received: from dede-linux-virt.corp.microsoft.com (unknown [131.107.160.54]) by linux.microsoft.com (Postfix) with ESMTPSA id 0969520B490D; Fri, 17 Jul 2020 16:09:47 -0700 (PDT) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com 0969520B490D From: Deven Bowers To: agk@redhat.com, axboe@kernel.dk, snitzer@redhat.com, jmorris@namei.org, serge@hallyn.com, zohar@linux.ibm.com, viro@zeniv.linux.org.uk, paul@paul-moore.com, eparis@redhat.com, jannh@google.com, dm-devel@redhat.com, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, linux-fsdevel@vger.kernel.org, linux-block@vger.kernel.org, linux-audit@redhat.com Date: Fri, 17 Jul 2020 16:09:32 -0700 Message-Id: <20200717230941.1190744-4-deven.desai@linux.microsoft.com> In-Reply-To: <20200717230941.1190744-1-deven.desai@linux.microsoft.com> References: <20200717230941.1190744-1-deven.desai@linux.microsoft.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.78 on 10.11.54.3 X-MIME-Autoconverted: from quoted-printable to 8bit by lists01.pubmisc.prod.ext.phx2.redhat.com id 06HNGmVW002236 X-loop: dm-devel@redhat.com Cc: sashal@kernel.org, mdsakib@microsoft.com, corbet@lwn.net, linux-kernel@vger.kernel.org, pasha.tatshin@soleen.com, nramas@linux.microsoft.com, tyhicks@linux.microsoft.com, jaskarankhurana@linux.microsoft.com Subject: [dm-devel] [RFC PATCH v4 03/12] security: add ipe lsm policy parser and policy loading X-BeenThere: dm-devel@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: device-mapper development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: dm-devel-bounces@redhat.com Errors-To: dm-devel-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.11 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Adds the policy parser and the policy loading to IPE, along with the related sysfs, securityfs entries, and audit events. Signed-off-by: Deven Bowers --- security/ipe/Kconfig | 2 + security/ipe/Makefile | 2 + security/ipe/ipe-audit.c | 87 +- security/ipe/ipe-audit.h | 8 + security/ipe/ipe-parse.c | 890 ++++++++++++++++++++ security/ipe/ipe-parse.h | 17 + security/ipe/ipe-policy.c | 148 ++++ security/ipe/ipe-policy.h | 13 +- security/ipe/ipe-prop-internal.h | 12 + security/ipe/ipe-property.h | 1 + security/ipe/ipe-secfs.c | 1308 ++++++++++++++++++++++++++++++ security/ipe/ipe-secfs.h | 16 + 12 files changed, 2500 insertions(+), 4 deletions(-) create mode 100644 security/ipe/ipe-parse.c create mode 100644 security/ipe/ipe-parse.h create mode 100644 security/ipe/ipe-policy.c create mode 100644 security/ipe/ipe-secfs.c create mode 100644 security/ipe/ipe-secfs.h diff --git a/security/ipe/Kconfig b/security/ipe/Kconfig index f9b8292a88ed..83cf0634043a 100644 --- a/security/ipe/Kconfig +++ b/security/ipe/Kconfig @@ -7,6 +7,8 @@ menuconfig SECURITY_IPE bool "Integrity Policy Enforcement (IPE)" depends on SECURITY && AUDIT select SYSTEM_DATA_VERIFICATION + select SECURITYFS + select CRYPTO_SHA1 help This option enables the Integrity Policy Enforcement subsystem allowing systems to enforce integrity having no dependencies diff --git a/security/ipe/Makefile b/security/ipe/Makefile index 078acd026ba9..7d6da33dd0c4 100644 --- a/security/ipe/Makefile +++ b/security/ipe/Makefile @@ -19,6 +19,8 @@ obj-$(CONFIG_SECURITY_IPE) += \ ipe-audit.o \ ipe-bp.o \ ipe-engine.o \ + ipe-parse.o \ + ipe-policy.o \ ipe-property.o \ ipe-hooks.o \ ipe-secfs.o \ diff --git a/security/ipe/ipe-audit.c b/security/ipe/ipe-audit.c index 230884e91d03..6b0b29c42f71 100644 --- a/security/ipe/ipe-audit.c +++ b/security/ipe/ipe-audit.c @@ -17,7 +17,8 @@ #include #define ACTION_STR(a) ((a) == ipe_action_allow ? "ALLOW" : "DENY") - +#define POLICY_LOAD_FSTR "IPE policy_name=\"%s\" policy_version=%hu.%hu.%hu sha1=" +#define POLICY_ACTIVATE_STR "IPE policy_name=\"%s\" policy_version=%hu.%hu.%hu" #define IPE_UNKNOWN "UNKNOWN" /* Keep in sync with ipe_op in ipe-hooks.h */ @@ -229,3 +230,87 @@ void ipe_audit_match(const struct ipe_engine_ctx *ctx, audit_log_end(ab); } + +/** + * ipe_audit_policy_load: Emit an audit event that an IPE policy has been + * loaded, with the name of the policy, the policy + * version triple, and a flat hash of the content. + * @pol: The parsed policy to derive the policy_name and policy_version + * triple. + * @raw: The raw content that was passed to the ipe.policy sysctl to derive + * the sha1 hash. + * @raw_size: the length of @raw. + * @tfm: shash structure allocated by the caller, used to fingerprint the + * policy being deployed + */ +void ipe_audit_policy_load(const struct ipe_policy *pol, const uint8_t *raw, + size_t raw_size, struct crypto_shash *tfm) +{ + int rc = 0; + struct audit_buffer *ab; + u8 digest[SHA1_DIGEST_SIZE]; + SHASH_DESC_ON_STACK(desc, tfm); + + ab = audit_log_start(audit_context(), GFP_KERNEL, + AUDIT_INTEGRITY_POLICY_LOAD); + if (!ab) + return; + + audit_log_format(ab, POLICY_LOAD_FSTR, pol->policy_name, + pol->policy_version.major, pol->policy_version.minor, + pol->policy_version.rev); + + desc->tfm = tfm; + + if (crypto_shash_init(desc) != 0) + goto err; + + if (crypto_shash_update(desc, raw, raw_size) != 0) + goto err; + + if (crypto_shash_final(desc, digest) != 0) + goto err; + + audit_log_n_hex(ab, digest, crypto_shash_digestsize(tfm)); + +err: + if (rc != 0) + audit_log_format(ab, "ERR(%d)", rc); + + audit_log_end(ab); +} + +/** + * ipe_audit_policy_activation: Emit an audit event that a specific policy + * was activated as the active policy. + * @pol: policy that is being activated + */ +void ipe_audit_policy_activation(const struct ipe_policy *pol) +{ + struct audit_buffer *ab; + + ab = audit_log_start(audit_context(), GFP_KERNEL, + AUDIT_INTEGRITY_POLICY_ACTIVATE); + + if (!ab) + return; + + audit_log_format(ab, POLICY_ACTIVATE_STR, pol->policy_name, + pol->policy_version.major, pol->policy_version.minor, + pol->policy_version.rev); + + audit_log_end(ab); +} + +/** + * ipe_audit_unset_remap: Emit an warning indicating that the operation + * represented by @op is unset and will implicitly + * allow everything under that op. + * @op: the operation to emit an audit message for. + */ +void ipe_audit_unset_remap(enum ipe_op op) +{ + pr_warn("op=%s default was unset, remapping to \"DEFAULT op=%s action=ALLOW\" for compatibility", + audit_op_names[op], + audit_op_names[op]); +} diff --git a/security/ipe/ipe-audit.h b/security/ipe/ipe-audit.h index c4fca5e2ab73..ca78a9578bf1 100644 --- a/security/ipe/ipe-audit.h +++ b/security/ipe/ipe-audit.h @@ -3,6 +3,7 @@ * Copyright (C) Microsoft Corporation. All rights reserved. */ +#include "ipe-prop-internal.h" #include "ipe-engine.h" #include "ipe-policy.h" @@ -15,4 +16,11 @@ void ipe_audit_match(const struct ipe_engine_ctx *ctx, enum ipe_match match_type, enum ipe_action action, const struct ipe_rule *rule); +void ipe_audit_policy_load(const struct ipe_policy *pol, const uint8_t *raw, + size_t raw_size, struct crypto_shash *tfm); + +void ipe_audit_policy_activation(const struct ipe_policy *pol); + +void ipe_audit_unset_remap(enum ipe_op op); + #endif /* IPE_AUDIT_H */ diff --git a/security/ipe/ipe-parse.c b/security/ipe/ipe-parse.c new file mode 100644 index 000000000000..ddcae6c7ada9 --- /dev/null +++ b/security/ipe/ipe-parse.c @@ -0,0 +1,890 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ + +#include "ipe.h" +#include "ipe-prop-internal.h" +#include "ipe-hooks.h" +#include "ipe-parse.h" +#include "ipe-property.h" +#include "ipe-audit.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define ALLOW_ACTION "ALLOW" +#define DENY_ACTION "DENY" +#define COMMENT_CHAR '#' +#define VER_FSTR "%hu.%hu.%hu" + +/* Internal Type Definitions */ +enum property_priority { + other = 0, + action = 1, + op = 2, + default_action = 3, + policy_ver = 4, + policy_name = 5, +}; + +struct token { + struct list_head next_tok; + const char *key; + enum property_priority key_priority; + const char *val; +}; + +/* Utility Functions */ +static inline bool is_quote(char c) +{ + return c == '"' || c == '\''; +} + +static inline bool valid_token(char *s) +{ + return !s || !strpbrk(s, "\"\'"); +} + +static inline bool is_default(const struct token *t) +{ + return !t->val && t->key_priority == default_action; +} + +static inline bool is_operation(const struct token *t) +{ + return t->val && t->key_priority == op; +} + +static inline bool is_action(const struct token *t) +{ + return t->val && t->key_priority == action; +} + +static inline bool is_name(const struct token *t) +{ + return t->val && t->key_priority == policy_name; +} + +static inline bool is_ver(const struct token *t) +{ + return t->val && t->key_priority == policy_ver; +} + +static int cmp_pri(void *priv, struct list_head *a, struct list_head *b) +{ + struct token *t_a = container_of(a, struct token, next_tok); + struct token *t_b = container_of(b, struct token, next_tok); + + return t_b->key_priority - t_a->key_priority; +} + +static char *trim_quotes(char *str) +{ + char s; + size_t len; + + if (!str) + return str; + + s = *str; + + if (is_quote(s)) { + len = strlen(str) - 1; + + if (str[len] != s) + return NULL; + + str[len] = '\0'; + ++str; + } + + return str; +} + +/** + * ipe_set_action: Set an action with error checking. + * @src: Valid pointer to the source location to set wih the result + * @set: Value to apply to @src, if valid + * + * Return: + * 0 - OK + * -EBADMSG - Attempting to set something that is already set + */ +static int ipe_set_action(enum ipe_action *src, enum ipe_action set) +{ + if (*src != ipe_action_unset) + return -EBADMSG; + + *src = set; + + return 0; +} + +/** + * ipe_insert_token: Allocate and append the key=value pair indicated by @val, + * to the list represented by @head. + * @val: Token to parse, of form "key=val". + * @head: Head of the list to insert the token structure into. + * + * If "=val" is omitted, this function will succeed, and the value set will be + * NULL. + * + * Return: + * 0 - OK + * -EBADMSG - Invalid policy syntax + * -ENOMEM - No Memory + */ +static int ipe_insert_token(char *val, struct list_head *head) +{ + char *key; + substring_t match[MAX_OPT_ARGS]; + struct token *tok; + const match_table_t prop_priorities = { + { policy_name, IPE_HEADER_POLICY_NAME }, + { policy_ver, IPE_HEADER_POLICY_VERSION}, + { op, IPE_PROPERTY_OPERATION }, + { default_action, IPE_PROPERTY_DEFAULT }, + { action, IPE_PROPERTY_ACTION }, + { other, NULL }, + }; + + key = strsep(&val, "="); + if (!key) + return -EBADMSG; + + tok = kzalloc(sizeof(*tok), GFP_KERNEL); + if (!tok) + return -ENOMEM; + + tok->key = key; + tok->val = trim_quotes(val); + + /* remap empty string */ + if (tok->val && !strlen(tok->val)) + tok->val = NULL; + + tok->key_priority = match_token(key, prop_priorities, match); + INIT_LIST_HEAD(&tok->next_tok); + + list_add_tail(&tok->next_tok, head); + + return 0; +} + +/** + * ipe_tokenize_line: Parse a line of text into a list of token structures. + * @line: Line to parse. + * @list: Head of the list to insert the token structure into. + * + * The final result will be sorted in the priority order definted by + * enum property_priorities to enforce policy structure. + * + * Return: + * 0 - OK + * -EBADMSG - Invalid policy syntax + * -ENOMEM - No Memory + * -ENOENT - No tokens were parsed + */ +static int ipe_tokenize_line(char *line, struct list_head *list) +{ + int rc = 0; + size_t i = 0; + size_t len = 0; + char *tok = NULL; + char quote = '\0'; + + len = strlen(line); + + for (i = 0; i < len; ++i) { + if (quote == '\0' && is_quote(line[i])) { + quote = line[i]; + continue; + } + + if (quote != '\0' && line[i] == quote) { + quote = '\0'; + continue; + } + + if (quote == '\0' && line[i] == COMMENT_CHAR) { + tok = NULL; + break; + } + + if (isgraph(line[i]) && !tok) + tok = &line[i]; + + if (quote == '\0' && isspace(line[i])) { + line[i] = '\0'; + + if (!tok) + continue; + + rc = ipe_insert_token(tok, list); + if (rc != 0) + return rc; + + tok = NULL; + } + } + + if (quote != '\0') + return -EBADMSG; + + if (tok) + ipe_insert_token(tok, list); + + if (list_empty(list)) + return -ENOENT; + + list_sort(NULL, list, cmp_pri); + + return 0; +} + +static inline int ipe_parse_version(const char *val, struct ipe_pol_ver *ver) +{ + if (sscanf(val, VER_FSTR, &ver->major, &ver->minor, &ver->rev) != 3) + return -EBADMSG; + + return 0; +} + +/** + * ipe_parse_action: Given a token, parse the value as if it were an 'action' + * token. + * @action: Token to parse to determine the action. + * + * Action tokens are of the form: action=(ALLOW|DENY) for more information + * about IPE policy, please see the documentation. + * + * Return: + * ipe_action_allow - OK + * ipe_action_deny - OK + * ipe_action_unset - ERR + */ +static enum ipe_action ipe_parse_action(struct token *action) +{ + if (!action->val) + return ipe_action_unset; + else if (!strcmp(action->val, ALLOW_ACTION)) + return ipe_action_allow; + else if (!strcmp(action->val, DENY_ACTION)) + return ipe_action_deny; + + return ipe_action_unset; +} + +/** + * ipe_parse_op: Given a token, parse the value as if it were an 'op' token. + * @op: Token to parse to determine the operation. + * + * "op" tokens are of the form: op=(EXECUTE|FIRMWARE|KEXEC_IMAGE|...) + * for more information about IPE policy, please see the documentation. + * + * Return: + * ipe_op_max - ERR + * otherwise - OK + */ +static enum ipe_op ipe_parse_op(struct token *op) +{ + substring_t match[MAX_OPT_ARGS]; + const match_table_t ops = { + { ipe_op_execute, IPE_OP_EXECUTE }, + { ipe_op_firmware, IPE_OP_FIRMWARE }, + { ipe_op_kexec_image, IPE_OP_KEXEC_IMAGE }, + { ipe_op_kexec_initramfs, IPE_OP_KEXEC_INITRAMFS }, + { ipe_op_x509, IPE_OP_X509_CERTIFICATE }, + { ipe_op_policy, IPE_OP_POLICY }, + { ipe_op_kmodule, IPE_OP_KMODULE }, + { ipe_op_kernel_read, IPE_OP_KERNEL_READ }, + { ipe_op_max, NULL }, + }; + + return match_token((char *)op->val, ops, match); +} + +/** + * ipe_set_default: Set the default of the policy, at various scope levels + * depending on the value of op. + * @op: Operation that was parsed. + * @pol: Policy to modify with the newly-parsed default action. + * @a: Action token (see parse_action) to parse to determine + * the default. + * + * Return: + * 0 - OK + * -EBADMSG - Invalid policy format + */ +static int ipe_set_default(enum ipe_op op, struct ipe_policy *pol, + struct token *a) +{ + int rc = 0; + size_t i = 0; + enum ipe_action act = ipe_parse_action(a); + + if (act == ipe_action_unset) + return -EBADMSG; + + if (op == ipe_op_max) + return ipe_set_action(&pol->def, act); + + if (op == ipe_op_kernel_read) { + for (i = ipe_op_firmware; i <= ipe_op_kmodule; ++i) { + rc = ipe_set_action(&pol->ops[i].def, act); + if (rc != 0) + return rc; + } + return 0; + } + + return ipe_set_action(&pol->ops[op].def, act); +} + +/** + * ipe_parse_default: Parse a default statement of an IPE policy modify @pol + * with the proper changes + * @tokens: List of tokens parsed from the line + * @pol: Policy to modify with the newly-parsed default action + * + * + * Return: + * 0 - OK + * -EBADMSG - Invalid policy format + * -ENOENT - Unknown policy structure + */ +static int ipe_parse_default(struct list_head *tokens, + struct ipe_policy *pol) +{ + struct token *f = NULL; + struct token *s = NULL; + struct token *t = NULL; + enum ipe_op i = ipe_op_max; + + f = list_first_entry(tokens, struct token, next_tok); + s = list_next_entry(f, next_tok); + if (is_action(s)) + return ipe_set_default(ipe_op_max, pol, s); + + i = ipe_parse_op(s); + if (i == ipe_op_max) + return -ENOENT; + + t = list_next_entry(s, next_tok); + if (is_action(t)) { + t = list_next_entry(s, next_tok); + return ipe_set_default(i, pol, t); + } + + return -ENOENT; +} + +/** + * ipe_free_token_list - Free a list of tokens, and then reinitialize @list + * dropping all tokens. + * @list: List to be freed. + */ +static void ipe_free_token_list(struct list_head *list) +{ + struct token *ptr, *next; + + list_for_each_entry_safe(ptr, next, list, next_tok) + kfree(ptr); + + INIT_LIST_HEAD(list); +} + +/** + * ipe_free_prop - Deallocator for an ipe_prop_container structure. + * @cont: Object to free. + */ +static void ipe_free_prop(struct ipe_prop_container *cont) +{ + if (IS_ERR_OR_NULL(cont)) + return; + + if (cont->prop->free_val) + cont->prop->free_val(&cont->value); + kfree(cont); +} + +/** + * ipe_alloc_prop: Allocator for a ipe_prop_container structure. + * @tok: Token structure representing the "key=value" pair of the property. + * + * Return: + * Pointer to ipe_rule - OK + * ERR_PTR(-ENOMEM) - Allocation failed + */ +static struct ipe_prop_container *ipe_alloc_prop(const struct token *tok) +{ + int rc = 0; + const struct ipe_property *prop = NULL; + struct ipe_prop_container *cont = NULL; + + prop = ipe_lookup_prop(tok->key); + if (!prop) { + rc = -ENOENT; + goto err; + } + + cont = kzalloc(sizeof(*cont), GFP_KERNEL); + if (!cont) { + rc = -ENOMEM; + goto err; + } + + INIT_LIST_HEAD(&cont->next); + + rc = prop->parse(tok->val, &cont->value); + if (rc != 0) + goto err; + + cont->prop = prop; + + return cont; +err: + ipe_free_prop(cont); + return ERR_PTR(rc); +} + +/** + * ipe_free_rule: Deallocator for an ipe_rule structure. + * @rule: Object to free. + */ +static void ipe_free_rule(struct ipe_rule *rule) +{ + struct ipe_prop_container *ptr; + struct list_head *l_ptr, *l_next; + + if (IS_ERR_OR_NULL(rule)) + return; + + list_for_each_safe(l_ptr, l_next, &rule->props) { + ptr = container_of(l_ptr, struct ipe_prop_container, next); + list_del(l_ptr); + ipe_free_prop(ptr); + } + + kfree(rule); +} + +/** + * ipe_alloc_rule: Allocate a ipe_rule structure, for operation @op, parsed + * from the first token in list @head. + * @op: Operation parsed from the first token in @head. + * @t: The first token in @head that was parsed. + * @head: List of remaining tokens to parse. + * + * Return: + * Valid ipe_rule pointer - OK + * ERR_PTR(-EBADMSG) - Invalid syntax + * ERR_PTR(-ENOMEM) - Out of memory + */ +static struct ipe_rule *ipe_alloc_rule(enum ipe_op op, struct token *t, + struct list_head *head) +{ + int rc = 0; + struct token *ptr; + enum ipe_action act; + struct ipe_rule *rule = NULL; + struct ipe_prop_container *prop = NULL; + + ptr = list_next_entry(t, next_tok); + if (!is_action(ptr)) { + rc = -EBADMSG; + goto err; + } + + act = ipe_parse_action(ptr); + if (act == ipe_action_unset) { + rc = -EBADMSG; + goto err; + } + + rule = kzalloc(sizeof(*rule), GFP_KERNEL); + if (!rule) { + rc = -ENOMEM; + goto err; + } + + INIT_LIST_HEAD(&rule->props); + INIT_LIST_HEAD(&rule->next); + rule->action = act; + rule->op = op; + + list_for_each_entry_continue(ptr, head, next_tok) { + prop = ipe_alloc_prop(ptr); + + if (IS_ERR(prop)) { + rc = PTR_ERR(prop); + goto err; + } + + list_add_tail(&prop->next, &rule->props); + } + + return rule; +err: + ipe_free_prop(prop); + ipe_free_rule(rule); + return ERR_PTR(rc); +} + +/** + * ipe_dup_prop: Duplicate an ipe_prop_container structure + * @p: Container to duplicate. + * + * This function is used to duplicate individual properties within a rule. + * It should only be called in operations that actually map to one or more + * operations. + * + * Return: + * Valid ipe_prop_container - OK + * ERR_PTR(-ENOMEM) - Out of memory + * Other Errors - see various property duplicator functions + */ +static +struct ipe_prop_container *ipe_dup_prop(const struct ipe_prop_container *p) +{ + int rc = 0; + struct ipe_prop_container *dup; + + dup = kzalloc(sizeof(*dup), GFP_KERNEL); + if (!dup) { + rc = -ENOMEM; + goto err; + } + + dup->prop = p->prop; + INIT_LIST_HEAD(&dup->next); + + rc = p->prop->dup(p->value, &dup->value); + if (rc != 0) + goto err; + + return dup; +err: + ipe_free_prop(dup); + return ERR_PTR(rc); +} + +/** + * ipe_dup_rule: Duplicate a policy rule, used for pseudo hooks like + * KERNEL_READ to map a policy rule across all hooks. + * @r: Rule to duplicate. + * + * Return: + * valid ipe_rule - OK + * ERR_PTR(-ENOMEM) - Out of memory + * Other Errors - See ipe_dup_prop + */ +static struct ipe_rule *ipe_dup_rule(const struct ipe_rule *r) +{ + int rc = 0; + struct ipe_rule *dup; + struct ipe_prop_container *ptr; + + dup = kzalloc(sizeof(*dup), GFP_KERNEL); + if (!dup) { + rc = -ENOMEM; + goto err; + } + + dup->op = r->op; + dup->action = r->action; + INIT_LIST_HEAD(&dup->props); + INIT_LIST_HEAD(&dup->next); + + list_for_each_entry(ptr, &r->props, next) { + struct ipe_prop_container *prop2; + + prop2 = ipe_dup_prop(ptr); + if (IS_ERR(prop2)) { + rc = PTR_ERR(prop2); + goto err; + } + + list_add_tail(&prop2->next, &dup->props); + } + + return dup; +err: + ipe_free_rule(dup); + return ERR_PTR(rc); +} + +/** + * ipe_free_policy: Deallocate an ipe_policy structure. + * @pol: Policy to free. + */ +void ipe_free_policy(struct ipe_policy *pol) +{ + size_t i; + struct ipe_rule *ptr; + struct ipe_rule_table *op; + struct list_head *l_ptr, *l_next; + + if (IS_ERR_OR_NULL(pol)) + return; + + for (i = 0; i < ARRAY_SIZE(pol->ops); ++i) { + op = &pol->ops[i]; + + list_for_each_safe(l_ptr, l_next, &op->rules) { + ptr = list_entry(l_ptr, struct ipe_rule, next); + list_del(l_ptr); + ipe_free_rule(ptr); + } + } + + kfree(pol->policy_name); + kfree(pol); +} + +/** + * ipe_alloc_policy: Give a list of tokens representing the first line of the + * token, attempt to parse it as an IPE policy header, and + * allocate a policy structure based on those values. + * @tokens: List of tokens parsed from the first line of the policy + * + * Return: + * Valid ipe_policy pointer - OK + * ERR_PTR(-ENOMEM) - Out of memory + * ERR_PTR(-EBADMSG) - Invalid policy syntax + */ +static struct ipe_policy *ipe_alloc_policy(struct list_head *tokens) +{ + size_t i; + int rc = 0; + struct token *name = NULL; + struct token *ver = NULL; + struct ipe_policy *lp = NULL; + + name = list_first_entry(tokens, struct token, next_tok); + if (!is_name(name)) { + rc = -EBADMSG; + goto err; + } + + if (list_is_singular(tokens)) { + rc = -EBADMSG; + goto err; + } + + ver = list_next_entry(name, next_tok); + if (!is_ver(ver)) { + rc = -EBADMSG; + goto err; + } + + lp = kzalloc(sizeof(*lp), GFP_KERNEL); + if (!lp) { + rc = -ENOMEM; + goto err; + } + + for (i = 0; i < ARRAY_SIZE(lp->ops); ++i) { + lp->ops[i].def = ipe_action_unset; + INIT_LIST_HEAD(&lp->ops[i].rules); + } + + lp->policy_name = kstrdup(name->val, GFP_KERNEL); + if (!lp->policy_name) { + rc = -ENOMEM; + goto err; + } + + rc = ipe_parse_version(ver->val, &lp->policy_version); + if (rc != 0) + goto err; + + lp->def = ipe_action_unset; + + return lp; +err: + ipe_free_policy(lp); + return ERR_PTR(rc); +} + +/** + * ipe_add_rule_for_range: Given a ipe_rule @r, duplicate @r and add the rule + * to @pol for the operation range @start to @end. + * @start: The starting point of the range to add the rule to. + * @end: The ending point of the range to add the rule to. + * @r: The rule to copy. + * @pol: Policy structure to modify with the result. + * + * This is @start to @end, inclusive. @r is still valid after this function, + * and should be freed if appropriate. + * + * Return: + * 0 - OK + * Other Errors - See ipe_dup_prop + */ +static int ipe_add_rule_for_range(enum ipe_op start, enum ipe_op end, + struct ipe_rule *r, struct ipe_policy *pol) +{ + enum ipe_op i; + struct ipe_rule *cpy = NULL; + + for (i = start; i <= end; ++i) { + cpy = ipe_dup_rule(r); + if (IS_ERR(cpy)) + return PTR_ERR(cpy); + + list_add_tail(&cpy->next, &pol->ops[i].rules); + } + + return 0; +} + +/** + * ipe_parse_line: Given a list of tokens, attempt to parse it into a rule + * structure, and add it to the passed-in ipe_policy structure. + * @tokens: List of tokens that were parsed. + * @pol: Policy structure to modify with the result. + * + * Return: + * 0 - OK + * -ENOENT - Unrecognized property + * -ENOMEM - Out of memory + * Other Errors - See ipe_dup_prop + */ +static int ipe_parse_line(struct list_head *tokens, + struct ipe_policy *pol) +{ + int rc = 0; + struct token *f; + enum ipe_op i = ipe_op_max; + struct ipe_rule *rule = NULL; + + f = list_first_entry(tokens, struct token, next_tok); + + switch (f->key_priority) { + case default_action: + rc = ipe_parse_default(tokens, pol); + break; + case op: + i = ipe_parse_op(f); + if (i == ipe_op_max) + return -ENOENT; + + if (list_is_singular(tokens)) + return -EBADMSG; + + rule = ipe_alloc_rule(i, f, tokens); + if (IS_ERR(rule)) { + rc = PTR_ERR(rule); + goto cleanup; + } + + if (i == ipe_op_kernel_read) { + rc = ipe_add_rule_for_range(ipe_op_firmware, + ipe_op_kmodule, rule, pol); + if (rc != 0) + goto cleanup; + } else { + list_add_tail(&rule->next, &pol->ops[i].rules); + rule = NULL; + } + break; + default: + return -ENOENT; + } + +cleanup: + ipe_free_rule(rule); + return rc; +} + +/** + * ipe_check_policy_defaults: Ensure all defaults in policy are set + * for every operation known to IPE. + * + * @p: Policy to check the defaults. + * + * Return: + * 0 - OK + * -EBADMSG - A default was left unset. + */ +static int ipe_check_policy_defaults(const struct ipe_policy *p) +{ + size_t i; + + if (p->def == ipe_action_unset) { + for (i = 0; i < ARRAY_SIZE(p->ops); ++i) { + if (p->ops[i].def == ipe_action_unset) { + ipe_audit_unset_remap((enum ipe_op)i); + return -EBADMSG; + } + } + } + + return 0; +} + +/** + * ipe_parse_policy: Given a string, parse the string into an IPE policy + * structure. + * @policy: NULL terminated string to parse. + * + * This function will modify @policy. + * + * Return: + * Valid ipe_policy structure - OK + * ERR_PTR(-EBADMSG) - Invalid Policy Syntax (Unrecoverable) + * ERR_PTR(-ENOMEM) - Out of Memory + */ +struct ipe_policy *ipe_parse_policy(char *policy) +{ + int rc = 0; + size_t i = 1; + char *p = NULL; + LIST_HEAD(t_list); + struct ipe_policy *local_p = NULL; + + while ((p = strsep(&policy, "\n\0")) != NULL) { + rc = ipe_tokenize_line(p, &t_list); + if (rc == -ENOENT) { + ++i; + continue; + } + if (rc != 0) + goto err; + + if (!local_p) { + local_p = ipe_alloc_policy(&t_list); + if (IS_ERR(local_p)) { + rc = PTR_ERR(local_p); + goto err; + } + } else { + rc = ipe_parse_line(&t_list, local_p); + if (rc) { + pr_warn("failed to parse line %zu", i); + goto err; + } + } + + ipe_free_token_list(&t_list); + ++i; + } + + rc = ipe_check_policy_defaults(local_p); + if (rc != 0) + goto err; + + return local_p; +err: + ipe_free_token_list(&t_list); + ipe_free_policy(local_p); + return ERR_PTR(rc); +} diff --git a/security/ipe/ipe-parse.h b/security/ipe/ipe-parse.h new file mode 100644 index 000000000000..b1a9bbd97534 --- /dev/null +++ b/security/ipe/ipe-parse.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ + +#include "ipe-policy.h" + +#include + +#ifndef IPE_PARSE_H +#define IPE_PARSE_H + +struct ipe_policy *ipe_parse_policy(char *policy); + +void ipe_free_policy(struct ipe_policy *pol); + +#endif /* IPE_AUDIT_H */ diff --git a/security/ipe/ipe-policy.c b/security/ipe/ipe-policy.c new file mode 100644 index 000000000000..8100a8aa3fa9 --- /dev/null +++ b/security/ipe/ipe-policy.c @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ + +#include "ipe.h" +#include "ipe-secfs.h" +#include "ipe-policy.h" +#include "ipe-parse.h" +#include "ipe-audit.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define VER_TO_UINT64(_major, _minor, _rev) \ + ((((((u64)(_major)) << 16) | ((u64)(_minor))) << 16) | ((u64)(_rev))) + +/** + * ipe_is_version_allowed: Determine if @new has a greater or equal + * policy version than @old. + * @old: The policy to compare against. + * @new: The policy staged to replace @old. + * + * Return: + * true - @new has a policy version >= than @old + * false - @new does not have a policy version >= than @old + */ +static bool ipe_is_version_allowed(const struct ipe_pol_ver *old, + const struct ipe_pol_ver *new) +{ + u64 old_ver = VER_TO_UINT64(old->major, old->minor, old->rev); + u64 new_ver = VER_TO_UINT64(new->major, new->minor, new->rev); + + return new_ver >= old_ver; +} + +/** + * ipe_is_valid_policy: determine if @old is allowed to replace @new. + * @old: policy that the @new is supposed to replace. Can be NULL. + * @new: the policy that is supposed to replace @new. + * + * Return: + * true - @new can replace @old + * false - @new cannot replace @old + */ +bool ipe_is_valid_policy(const struct ipe_policy *old, + const struct ipe_policy *new) +{ + if (old) + return ipe_is_version_allowed(&old->policy_version, + &new->policy_version); + return true; +} + +/** + * ipe_is_active_policy: Determine if @policy is the currently active policy. + * @policy: Policy to check if it's the active policy. + * + * Return: + * true - @policy is the active policy + * false - @policy is not the active policy + */ +bool ipe_is_active_policy(const struct ipe_policy *policy) +{ + return rcu_access_pointer(ipe_active_policy) == policy; +} + +/** + * ipe_update_active_policy: Determine if @old is the active policy, and update + * the active policy if necessary. + * @old: The previous policy that the update is trying to replace. + * @new: The new policy attempting to replace @old. + * + * If @old is not the active policy, nothing will be done. + * + * Return: + * 0 - OK + * -EBADMSG - Invalid Policy + */ +int ipe_update_active_policy(const struct ipe_policy *old, + const struct ipe_policy *new) +{ + const struct ipe_policy *curr = NULL; + + lockdep_assert_held(&ipe_policy_lock); + + /* no active policy, safe to update */ + if (!rcu_access_pointer(ipe_active_policy)) + return 0; + + curr = rcu_dereference_protected(ipe_active_policy, + lockdep_is_held(&ipe_policy_lock)); + + if (curr == old) { + if (!ipe_is_valid_policy(curr, new)) + return -EINVAL; + + ipe_audit_policy_activation(new); + + (void)rcu_replace_pointer(ipe_active_policy, new, + lockdep_is_held(&ipe_policy_lock)); + } + + return 0; +} + +/** + * ipe_activate_policy: Set a specific policy as the active policy. + * @pol: The policy to set as the active policy. + * + * This is only called by the sysctl "ipe.active_policy". + * + * Return: + * 0 - OK + * -EINVAL - Policy that is being activated is lower in version than + * currently running policy. + */ +int ipe_activate_policy(const struct ipe_policy *pol) +{ + const struct ipe_policy *curr = NULL; + + lockdep_assert_held(&ipe_policy_lock); + + curr = rcu_dereference_protected(ipe_active_policy, + lockdep_is_held(&ipe_policy_lock)); + + /* + * User-set policies must be >= to current running policy. + */ + if (!ipe_is_valid_policy(curr, pol)) + return -EINVAL; + + ipe_audit_policy_activation(pol); + + /* cleanup of this pointer is handled by the secfs removal */ + (void)rcu_replace_pointer(ipe_active_policy, pol, + lockdep_is_held(&ipe_policy_lock)); + + return 0; +} diff --git a/security/ipe/ipe-policy.h b/security/ipe/ipe-policy.h index c0c9f2962c92..b3ef3d3d2e7f 100644 --- a/security/ipe/ipe-policy.h +++ b/security/ipe/ipe-policy.h @@ -14,9 +14,6 @@ #ifndef IPE_POLICY_H #define IPE_POLICY_H -#define IPE_HEADER_POLICY_NAME "policy_name" -#define IPE_HEADER_POLICY_VERSION "policy_version" - extern const char *const ipe_boot_policy; extern const struct ipe_policy *ipe_active_policy; @@ -59,4 +56,14 @@ struct ipe_policy { struct ipe_rule_table ops[ipe_op_max - 1]; }; +bool ipe_is_valid_policy(const struct ipe_policy *old, + const struct ipe_policy *new); + +bool ipe_is_active_policy(const struct ipe_policy *policy); + +int ipe_update_active_policy(const struct ipe_policy *old, + const struct ipe_policy *new); + +int ipe_activate_policy(const struct ipe_policy *policy); + #endif /* IPE_POLICY_H */ diff --git a/security/ipe/ipe-prop-internal.h b/security/ipe/ipe-prop-internal.h index 95a2081e77ee..0d5cde61784c 100644 --- a/security/ipe/ipe-prop-internal.h +++ b/security/ipe/ipe-prop-internal.h @@ -10,10 +10,20 @@ #ifndef IPE_PROPERTY_INTERNAL_H #define IPE_PROPERTY_INTERNAL_H +/* built-in tokens */ +#define IPE_HEADER_POLICY_NAME "policy_name" +#define IPE_HEADER_POLICY_VERSION "policy_version" #define IPE_PROPERTY_OPERATION "op" #define IPE_PROPERTY_DEFAULT "DEFAULT" #define IPE_PROPERTY_ACTION "action" +/* Version strings for built-in tokens */ +#define IPE_PROPERTY_OPERATION_VER IPE_PROPERTY_OPERATION "=1" +#define IPE_PROPERTY_ACTION_VER IPE_PROPERTY_ACTION "=1" +#define IPE_PROPERTY_DEFAULT_VER IPE_PROPERTY_DEFAULT "=1" +#define IPE_HEADER_POLICY_NAME_VER IPE_HEADER_POLICY_NAME "=1" +#define IPE_HEADER_POLICY_VERSION_VER IPE_HEADER_POLICY_VERSION "=1" + #define IPE_OP_EXECUTE "EXECUTE" #define IPE_OP_FIRMWARE "FIRMWARE" #define IPE_OP_KEXEC_IMAGE "KEXEC_IMAGE" @@ -23,6 +33,8 @@ #define IPE_OP_KMODULE "KMODULE" #define IPE_OP_KERNEL_READ "KERNEL_READ" +#define IPE_UNKNOWN "UNKNOWN" + struct ipe_prop_reg { struct rb_node node; const struct ipe_property *prop; diff --git a/security/ipe/ipe-property.h b/security/ipe/ipe-property.h index cf570d52d0d2..8bb2e2c1619c 100644 --- a/security/ipe/ipe-property.h +++ b/security/ipe/ipe-property.h @@ -86,6 +86,7 @@ typedef void (*ipe_free_value)(void **value); struct ipe_property { const char *const property_name; + u16 version; ipe_property_evaluator eval; ipe_property_audit rule_audit; ipe_ctx_audit ctx_audit; diff --git a/security/ipe/ipe-secfs.c b/security/ipe/ipe-secfs.c new file mode 100644 index 000000000000..102444b8cc59 --- /dev/null +++ b/security/ipe/ipe-secfs.c @@ -0,0 +1,1308 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ + +#include "ipe.h" +#include "ipe-parse.h" +#include "ipe-secfs.h" +#include "ipe-policy.h" +#include "ipe-audit.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IPE_ROOT "ipe" +#define IPE_POLICIES "policies" +#define NEW_POLICY "new_policy" +#define IPE_PROPERTY_CFG "property_config" +#define IPE_SUCCESS_AUDIT "success_audit" +#define IPE_ENFORCE "enforce" + +#define IPE_FULL_CONTENT "raw" +#define IPE_INNER_CONTENT "content" +#define IPE_ACTIVE_POLICY "active" +#define IPE_DELETE_POLICY "delete" + +struct ipe_policy_node { + u8 *data; + size_t data_len; + const u8 *content; + size_t content_size; + + struct ipe_policy *parsed; +}; + +/* root directory */ +static struct dentry *securityfs_root __ro_after_init; + +/* subdirectory containing policies */ +static struct dentry *policies_root __ro_after_init; + +/* boot policy */ +static struct dentry *boot_policy_node __ro_after_init; + +/* top-level IPE commands */ +static struct dentry *new_policy_node __ro_after_init; +static struct dentry *property_cfg_node __ro_after_init; +static struct dentry *enforce_node __ro_after_init; +static struct dentry *success_audit_node __ro_after_init; + +/* lock for synchronizing writers across ipe policy */ +DEFINE_MUTEX(ipe_policy_lock); + +/** + * get_int_user - retrieve a single integer from a string located in userspace. + * @data: usespace address to parse for an integer + * @len: length of @data + * @offset: offset into @data. Unused. + * @value: pointer to a value to propagate with the result + * + * Return: + * 0 - OK + * -ENOMEM - allocation failed + * -EINVAL - more than 1 integer was present + * Other - see strnpy_from_user + */ +static int get_int_user(const char __user *data, size_t len, loff_t *offset, + int *value) +{ + int rc = 0; + char *buffer = NULL; + + buffer = kzalloc(len + 1, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + rc = strncpy_from_user(buffer, data, len + 1); + if (rc < 0) + goto out; + + rc = kstrtoint(buffer, 10, value); +out: + kfree(buffer); + return rc; +} + +/** + * ipe_get_audit_mode - retrieve the current value of the success_audit flag + * as a string representation. + * @f: The file structure representing the securityfs entry. Unused. + * @data: userspace buffer to place the result + * @len: length of @data + * @offset: offset into @data + * + * This is the handler for the 'read' syscall on the securityfs node, + * ipe/success_audit + * + * Return: + * > 0 - OK + * < 0 - Error, see simple_read_from_buffer + */ +static ssize_t ipe_get_audit_mode(struct file *f, char __user *data, size_t len, + loff_t *offset) +{ + char tmp[3] = { 0 }; + + snprintf(tmp, ARRAY_SIZE(tmp), "%c\n", (ipe_success_audit) ? '1' : '0'); + + return simple_read_from_buffer(data, len, offset, tmp, + ARRAY_SIZE(tmp)); +} + +/** + * ipe_set_audit_mode - change the value of the ipe_success_audit flag. + * @f: The file structure representing the securityfs entry + * @data: userspace buffer containing value to be set. Should be "1" or "0". + * @len: length of @data + * @offset: offset into @data + * + * Return: + * > 0 - OK + * -EPERM - if MAC system available, missing CAP_MAC_ADMIN. + * -EINVAL - value written was not "1" or "0". + */ +static ssize_t ipe_set_audit_mode(struct file *f, const char __user *data, size_t len, + loff_t *offset) +{ + int v = 0; + int rc = 0; + + if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN)) + return -EPERM; + + rc = get_int_user(data, len, offset, &v); + if (rc) + return rc; + + if (v != 0 && v != 1) + return -EINVAL; + + ipe_success_audit = v == 1; + + return len; +} + +static const struct file_operations audit_ops = { + .read = ipe_get_audit_mode, + .write = ipe_set_audit_mode +}; + +#ifdef CONFIG_SECURITY_IPE_PERMISSIVE_SWITCH + +/** + * ipe_get_enforce - retrieve the current value of the ipe_enforce flag + * as a string representation. + * @f: The file structure representing the securityfs entry. Unused. + * @data: userspace buffer to place the result + * @len: length of @data + * @offset: offset into @data + * + * This is the handler for the 'read' syscall on the securityfs node, + * ipe/enforce + * + * Return: + * > 0 - OK + * < 0 - Error, see simple_read_from_buffer + */ +static ssize_t ipe_get_enforce(struct file *f, char __user *data, size_t len, + loff_t *offset) +{ + char tmp[3] = { 0 }; + + snprintf(tmp, ARRAY_SIZE(tmp), "%c\n", (ipe_enforce) ? '1' : '0'); + + return simple_read_from_buffer(data, len, offset, tmp, + ARRAY_SIZE(tmp)); +} + +/** + * ipe_set_enforce - change the value of the ipe_enforce flag. + * @f: The file structure representing the securityfs entry + * @data: userspace buffer containing value to be set. Should be "1" or "0". + * @len: length of @data + * @offset: offset into @data + * + * Return: + * > 0 - OK + * -EPERM - if MAC system available, missing CAP_MAC_ADMIN. + * -EINVAL - value written was not "1" or "0". + */ +static ssize_t ipe_set_enforce(struct file *f, const char __user *data, size_t len, + loff_t *offset) +{ + int v = 0; + int rc = 0; + bool ret = 0; + + if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN)) + return -EPERM; + + rc = get_int_user(data, len, offset, &v); + if (rc) + return rc; + + if (v != 0 && v != 1) + return -EINVAL; + + ret = v == 1; + + if (ret != ipe_enforce) + ipe_audit_mode(); + + ipe_enforce = ret; + + return len; +} + +static const struct file_operations enforce_ops = { + .read = ipe_get_enforce, + .write = ipe_set_enforce +}; + +/** + * ipe_init_enforce_node - Wrapper around securityfs_create_file for the + * ipe/enforce securityfs node. + * @root: securityfs node that is the parent of the new node to be created + * + * This allows this function to be no-op'd when the permissive switch is + * disabled. + * + * Return: + * See securityfs_create_file. + */ +static inline struct dentry *ipe_init_enforce_node(struct dentry *root) +{ + return securityfs_create_file(IPE_ENFORCE, 0644, root, NULL, + &enforce_ops); +} + +#else + +/** + * ipe_init_enforce_node - Wrapper around securityfs_create_file for the + * ipe/enforce securityfs node. + * @root: Unused + * + * This allows this function to be no-op'd when the permissive switch is + * disabled. + * + * Return: + * NULL. + */ +static inline struct dentry *ipe_init_enforce_node(struct dentry *root) +{ + return NULL; +} + +#endif /* CONFIG_SECURITY_IPE_PERMISSIVE_SWITCH */ + +/** + * retrieve_backed_dentry: Retrieve a dentry with a backing inode, identified + * by @name, under @parent. + * @name: Name of the dentry under @parent. + * @parent: The parent dentry to search under for @name. + * @size: Length of @name. + * + * This takes a reference to the returned dentry. Caller needs to call dput + * to drop the reference. + * + * Return: + * valid dentry - OK + * ERR_PTR - Error, see lookup_one_len_unlocked + * NULL - No backing inode was found + */ +static struct dentry *retrieve_backed_dentry(const char *name, + struct dentry *parent, + size_t size) +{ + struct dentry *tmp = NULL; + + tmp = lookup_one_len_unlocked(name, parent, size); + if (IS_ERR(tmp)) + return tmp; + + if (!d_really_is_positive(tmp)) + return NULL; + + return tmp; +} + +/** + * alloc_size_cb: Callback for determining the allocation size of the grammar + * buffer + * @prop: ipe_property structure to determine allocation size + * @ctx: void* representing a size_t* to add the allocation size to. + * + * Return: + * 0 - Always + */ +static int alloc_size_cb(const struct ipe_property *prop, void *ctx) +{ + size_t *ref = ctx; + char tmp[6] = { 0 }; + + snprintf(tmp, ARRAY_SIZE(tmp), "%d", prop->version); + + /* property_name=u16\n */ + *ref += strlen(prop->property_name) + strlen(tmp) + 2; + + return 0; +} + +/** + * build_cfg_str: Callback to populate the previously-allocated string + * buffer for ipe's grammar version with the content. + * @prop: ipe_property structure to determine allocation size + * @ctx: void* representing a char* to append the population to. + * + * Return: + * 0 - Always + */ +static int build_cfg_str(const struct ipe_property *prop, void *ctx) +{ + char *ref = (char *)ctx; + char tmp[6] = { 0 }; + + snprintf(tmp, ARRAY_SIZE(tmp), "%d", prop->version); + strcat(ref, prop->property_name); + strcat(ref, "="); + strcat(ref, tmp); + strcat(ref, "\n"); + + return 0; +} + +/** + * create_new_prop_cfg: create a new property configuration string for consumers + * of IPE policy. + * + * This function will iterate over all currently registered properties, and + * return a string of form: + * + * property1=version1\n + * property2=version2\n + * ... + * propertyN=versionN + * + * Where propertyX is the property_name and versionX is the version associated. + * + * Return: + * !ERR_PTR - Success + * ERR_PTR(-ENOMEM) - Allocation Failed + */ +static char *create_new_prop_cfg(void) +{ + size_t i; + ssize_t rc = 0; + size_t alloc = 0; + char *ret = NULL; + const char *const built_ins[] = { + IPE_PROPERTY_OPERATION_VER, + IPE_PROPERTY_ACTION_VER, + IPE_PROPERTY_DEFAULT_VER, + IPE_HEADER_POLICY_NAME_VER, + IPE_HEADER_POLICY_VERSION_VER + }; + + for (i = 0; i < ARRAY_SIZE(built_ins); ++i) + alloc += strlen(built_ins[i]) + 1; /* \n */ + + (void)ipe_for_each_prop(alloc_size_cb, (void *)&alloc); + ++alloc; /* null for strcat */ + + ret = kzalloc(alloc, GFP_KERNEL); + if (!ret) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < ARRAY_SIZE(built_ins); ++i) { + strcat(ret, built_ins[i]); + strcat(ret, "\n"); + } + + rc = ipe_for_each_prop(build_cfg_str, (void *)ret); + if (rc) + goto err; + + return ret; +err: + kfree(ret); + return ERR_PTR(rc); +} + +/** + * ipe_get_prop_cfg: Get (or allocate if one does not exist) the property + * configuration string for IPE. + * + * @f: File representing the securityfs entry. + * @data: User mode buffer to place the configuration string. + * @len: Length of @data. + * @offset: Offset into @data. + * + * As this string can only change on a new kernel build, this string + * is cached in the i_private field of @f's inode for subsequent calls. + * + * Return: + * < 0 - Error + * > 0 - Success, bytes written to @data + */ +static ssize_t ipe_get_prop_cfg(struct file *f, char __user *data, size_t size, + loff_t *offset) +{ + ssize_t rc = 0; + const char *cfg = NULL; + struct inode *grammar = d_inode(property_cfg_node); + + inode_lock(grammar); + + /* + * This can only change with a new kernel build, + * so cache the result in i->private + */ + if (IS_ERR_OR_NULL(grammar->i_private)) { + grammar->i_private = create_new_prop_cfg(); + if (IS_ERR(grammar->i_private)) { + rc = PTR_ERR(grammar->i_private); + goto out; + } + } + cfg = (const char *)grammar->i_private; + + rc = simple_read_from_buffer(data, size, offset, cfg, strlen(cfg)); + +out: + inode_unlock(grammar); + return rc; +} + +static const struct file_operations prop_cfg_ops = { + .read = ipe_get_prop_cfg +}; + +/** + * ipe_free_policy_node: Free an ipe_policy_node structure allocated by + * ipe_alloc_policy_node. + * @n: ipe_policy_node to free + */ +static void ipe_free_policy_node(struct ipe_policy_node *n) +{ + if (IS_ERR_OR_NULL(n)) + return; + + ipe_free_policy(n->parsed); + kfree(n->data); + + kfree(n); +} + +/** + * alloc_callback: Callback given to verify_pkcs7_signature function to set + * the inner content reference and parse the policy. + * @ctx: "ipe_policy_node" to set inner content, size and parsed policy of. + * @data: Start of PKCS#7 inner content. + * @len: Length of @data. + * @asn1hdrlen: Unused. + * + * Return: + * 0 - OK + * ERR_PTR(-EBADMSG) - Invalid policy syntax + * ERR_PTR(-ENOMEM) - Out of memory + */ +static int alloc_callback(void *ctx, const void *data, size_t len, + size_t asn1hdrlen) +{ + char *cpy = NULL; + struct ipe_policy *pol = NULL; + struct ipe_policy_node *n = (struct ipe_policy_node *)ctx; + + n->content = (const u8 *)data; + n->content_size = len; + + if (len == 0) + return -EBADMSG; + + cpy = kzalloc(len + 1, GFP_KERNEL); + if (!cpy) + return -ENOMEM; + + (void)memcpy(cpy, data, len); + + pol = ipe_parse_policy(cpy); + if (IS_ERR(pol)) { + kfree(cpy); + return PTR_ERR(pol); + } + + n->parsed = pol; + kfree(cpy); + return 0; +} + +/** + * ipe_delete_policy_tree - delete the policy subtree under + * $securityfs/ipe/policies. + * @policy_root: the policy root directory, i.e. + * $securityfs/ipe/policies/$policy_name + * + * Return: + * 0 - OK + * -EPERM - Tree being deleted is the active policy + * -ENOENT - A subnode is missing under the tree. + * Other - see lookup_one_len_unlocked. + */ +static int ipe_delete_policy_tree(struct dentry *policy_root) +{ + int rc = 0; + struct dentry *raw = NULL; + struct dentry *active = NULL; + struct dentry *content = NULL; + struct dentry *delete = NULL; + const struct ipe_policy_node *target = NULL; + + /* ensure the active policy cannot be changed */ + lockdep_assert_held(&ipe_policy_lock); + + /* fail if it's the active policy */ + target = (const struct ipe_policy_node *)d_inode(policy_root)->i_private; + if (ipe_is_active_policy(target->parsed)) { + rc = -EPERM; + goto out; + } + + raw = retrieve_backed_dentry(IPE_FULL_CONTENT, policy_root, + strlen(IPE_FULL_CONTENT)); + if (IS_ERR_OR_NULL(raw)) { + rc = IS_ERR(raw) ? PTR_ERR(raw) : -ENOENT; + goto out; + } + + content = retrieve_backed_dentry(IPE_INNER_CONTENT, policy_root, + strlen(IPE_INNER_CONTENT)); + if (IS_ERR_OR_NULL(content)) { + rc = IS_ERR(content) ? PTR_ERR(content) : -ENOENT; + goto out_free_raw; + } + + active = retrieve_backed_dentry(IPE_ACTIVE_POLICY, policy_root, + strlen(IPE_ACTIVE_POLICY)); + if (IS_ERR_OR_NULL(active)) { + rc = IS_ERR(active) ? PTR_ERR(active) : -ENOENT; + goto out_free_content; + } + + delete = retrieve_backed_dentry(IPE_DELETE_POLICY, policy_root, + strlen(IPE_DELETE_POLICY)); + if (IS_ERR_OR_NULL(active)) { + rc = IS_ERR(active) ? PTR_ERR(active) : -ENOENT; + goto out_free_active; + } + + inode_lock(d_inode(policy_root)); + ipe_free_policy_node(d_inode(policy_root)->i_private); + d_inode(policy_root)->i_private = NULL; + inode_unlock(d_inode(policy_root)); + + /* drop references from acquired in this function */ + dput(raw); + dput(content); + dput(policy_root); + dput(active); + dput(delete); + + /* drop securityfs' references */ + securityfs_remove(raw); + securityfs_remove(content); + securityfs_remove(policy_root); + securityfs_remove(active); + securityfs_remove(delete); + + return rc; + +out_free_active: + dput(active); +out_free_content: + dput(content); +out_free_raw: + dput(raw); +out: + return rc; +} + +/** + * ipe_delete_policy: Delete a policy, which is stored in this file's parent + * dentry's inode. + * @f: File representing the securityfs entry. + * @data: Buffer containing the value 1. + * @len: sizeof(u8). + * @offset: Offset into @data. + * + * Return: + * > 0 - OK + * -ENOMEM - Out of memory + * -EINVAL - Incorrect parameter + * -EPERM - Policy is active + * -ENOENT - A policy subnode does not exist + * -EPERM - if a MAC subsystem is enabled, missing CAP_MAC_ADMIN + * Other - See retrieve_backed_dentry + */ +static ssize_t ipe_delete_policy(struct file *f, const char __user *data, + size_t len, loff_t *offset) +{ + int v = 0; + ssize_t rc = 0; + struct inode *policy_i = NULL; + struct dentry *policy_root = NULL; + + if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN)) + return -EPERM; + + rc = get_int_user(data, len, offset, &v); + if (rc) + return rc; + + if (v != 1) + return -EINVAL; + + policy_root = f->f_path.dentry->d_parent; + policy_i = d_inode(policy_root); + + if (!policy_i->i_private) + return -ENOENT; + + /* guarantee active policy cannot change */ + mutex_lock(&ipe_policy_lock); + + rc = ipe_delete_policy_tree(policy_root); + if (rc) + goto out_unlock; + + mutex_unlock(&ipe_policy_lock); + synchronize_rcu(); + + return len; + +out_unlock: + mutex_unlock(&ipe_policy_lock); + return rc; +} + +static const struct file_operations policy_delete_ops = { + .write = ipe_delete_policy +}; + +/** + * ipe_alloc_policy_node: Allocate a new ipe_policy_node structure. + * @data: Raw enveloped PKCS#7 data that represents the policy. + * @len: Length of @data. + * + * Return: + * valid ipe_policy_node - OK + * ERR_PTR(-EBADMSG) - Invalid policy syntax + * ERR_PTR(-ENOMEM) - Out of memory + */ +static struct ipe_policy_node *ipe_alloc_policy_node(const u8 *data, + size_t len) +{ + int rc = 0; + struct ipe_policy_node *node = NULL; + + node = kzalloc(sizeof(*node), GFP_KERNEL); + if (!node) { + rc = -ENOMEM; + goto out; + } + + node->data_len = len; + node->data = kmemdup(data, len, GFP_KERNEL); + if (!node->data) { + rc = -ENOMEM; + goto out2; + } + + rc = verify_pkcs7_signature(node->content, node->content_size, + node->data, node->data_len, NULL, + VERIFYING_UNSPECIFIED_SIGNATURE, + alloc_callback, node); + if (rc != 0) + goto out2; + + return node; +out2: + ipe_free_policy_node(node); +out: + return ERR_PTR(rc); +} + +/** + * ipe_secfs_rd_policy: Read the raw content (full enveloped PKCS7) data of + * the policy stored within the file's parent inode. + * @f: File representing the securityfs entry. + * @data: User mode buffer to place the raw pkcs7. + * @len: Length of @data. + * @offset: Offset into @data. + * + * Return: + * > 0 - OK + * -ENOMEM - Out of memory + */ +static ssize_t ipe_secfs_rd_policy(struct file *f, char __user *data, + size_t size, loff_t *offset) +{ + ssize_t rc = 0; + size_t avail = 0; + struct inode *root = NULL; + const struct ipe_policy_node *node = NULL; + + root = d_inode(f->f_path.dentry->d_parent); + + inode_lock_shared(root); + node = (const struct ipe_policy_node *)root->i_private; + + avail = node->data_len; + rc = simple_read_from_buffer(data, size, offset, node->data, avail); + + inode_unlock_shared(root); + return rc; +} + +/** + * ipe_secfs_ud_policy: Update a policy in place with a new PKCS7 policy. + * @f: File representing the securityfs entry. + * @data: Buffer user mode to place the raw pkcs7. + * @len: Length of @data. + * @offset: Offset into @data. + * + * Return: + * 0 - OK + * -EBADMSG - Invalid policy format + * -ENOMEM - Out of memory + * -EPERM - if a MAC subsystem is enabled, missing CAP_MAC_ADMIN + * -EINVAL - Incorrect policy name for this node, or version is < current + */ +static ssize_t ipe_secfs_ud_policy(struct file *f, const char __user *data, + size_t len, loff_t *offset) +{ + ssize_t rc = 0; + u8 *cpy = NULL; + struct inode *root = NULL; + struct crypto_shash *tfm = NULL; + struct ipe_policy_node *new = NULL; + struct ipe_policy_node *old = NULL; + + if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN)) + return -EPERM; + + cpy = memdup_user(data, len); + if (IS_ERR(cpy)) + return PTR_ERR(cpy); + + new = ipe_alloc_policy_node(cpy, len); + if (IS_ERR(new)) { + rc = PTR_ERR(new); + goto out_free_cpy; + } + + tfm = crypto_alloc_shash("sha1", 0, 0); + if (IS_ERR(tfm)) + goto out_free_cpy; + + root = d_inode(f->f_path.dentry->d_parent); + inode_lock(root); + mutex_lock(&ipe_policy_lock); + + old = (struct ipe_policy_node *)root->i_private; + + if (strcmp(old->parsed->policy_name, new->parsed->policy_name)) { + rc = -EINVAL; + goto out_unlock_inode; + } + + if (!ipe_is_valid_policy(old->parsed, new->parsed)) { + rc = -EINVAL; + goto out_unlock_inode; + } + + rc = ipe_update_active_policy(old->parsed, new->parsed); + if (rc != 0) + goto out_unlock_inode; + + ipe_audit_policy_load(new->parsed, new->data, new->data_len, tfm); + swap(root->i_private, new); + + mutex_unlock(&ipe_policy_lock); + synchronize_rcu(); + + inode_unlock(root); + kfree(cpy); + ipe_free_policy_node(new); + crypto_free_shash(tfm); + + return len; + +out_unlock_inode: + mutex_unlock(&ipe_policy_lock); + inode_unlock(root); + ipe_free_policy_node(new); + crypto_free_shash(tfm); +out_free_cpy: + kfree(cpy); + return rc; +} + +static const struct file_operations policy_raw_ops = { + .read = ipe_secfs_rd_policy, + .write = ipe_secfs_ud_policy +}; + +/** + * ipe_secfs_rd_content: Read the inner content of the enveloped PKCS7 data, + * representing the IPE policy. + * @f: File representing the securityfs entry. + * @data: User mode buffer to place the inner content of the pkcs7 data. + * @len: Length of @data. + * @offset: Offset into @data. + * + * Return: + * > 0 - OK + * -ENOMEM - Out of memory + */ +static ssize_t ipe_secfs_rd_content(struct file *f, char __user *data, + size_t size, loff_t *offset) +{ + ssize_t rc = 0; + size_t avail = 0; + struct inode *root = NULL; + const struct ipe_policy_node *node = NULL; + + root = d_inode(f->f_path.dentry->d_parent); + + inode_lock_shared(root); + node = (const struct ipe_policy_node *)root->i_private; + + avail = node->content_size; + rc = simple_read_from_buffer(data, size, offset, node->content, avail); + + inode_unlock_shared(root); + return rc; +} + +static const struct file_operations policy_content_ops = { + .read = ipe_secfs_rd_content +}; + +/** + * ipe_get_active - return a string representation of whether a policy + * is active. + * @f: File struct representing the securityfs node. Unused. + * @data: buffer to place the result. + * @len: length of @data. + * @offset: offset into @data. + * + * This is the 'read' syscall handler for + * $securityfs/ipe/policies/$policy_name/active + * + * Return: + * > 0 - OK + * < 0 - see simple_read_from_buffer. + */ +static ssize_t ipe_get_active(struct file *f, char __user *data, size_t len, + loff_t *offset) +{ + ssize_t rc = 0; + char tmp[3] = { 0 }; + struct inode *root = NULL; + const struct ipe_policy_node *node = NULL; + + root = d_inode(f->f_path.dentry->d_parent); + inode_lock_shared(root); + + node = (const struct ipe_policy_node *)root->i_private; + + snprintf(tmp, ARRAY_SIZE(tmp), "%c\n", + ipe_is_active_policy(node->parsed) ? '1' : '0'); + + rc = simple_read_from_buffer(data, len, offset, tmp, + ARRAY_SIZE(tmp)); + + inode_unlock_shared(root); + + return rc; +} + +/** + * ipe_set_active - mark a policy as active, causing IPE to start enforcing + * this policy. + * @f: File struct representing the securityfs node. + * @data: buffer containing data written to the securityfs node.. + * @len: length of @data. + * @offset: offset into @data. + * + * This is the 'write' syscall handler for + * $securityfs/ipe/policies/$policy_name/active + * + * Return: + * > 0 - OK + * -EINVAL - Value written is not "1". + * -EPERM - if MAC system is enabled, missing CAP_MAC_ADMIN. + * Other - see ipe_activate_policy, get_int_user + */ +static ssize_t ipe_set_active(struct file *f, const char __user *data, size_t len, + loff_t *offset) +{ + int v = 0; + ssize_t rc = 0; + struct inode *root = NULL; + const struct ipe_policy_node *node = NULL; + + if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN)) + return -EPERM; + + rc = get_int_user(data, len, offset, &v); + if (rc) + return rc; + + if (v != 1) + return -EINVAL; + + root = d_inode(f->f_path.dentry->d_parent); + mutex_lock(&ipe_policy_lock); + inode_lock_shared(root); + + node = (const struct ipe_policy_node *)root->i_private; + rc = ipe_activate_policy(node->parsed); + + inode_unlock_shared(root); + mutex_unlock(&ipe_policy_lock); + synchronize_rcu(); + + return len; +} + +static const struct file_operations policy_active_ops = { + .read = ipe_get_active, + .write = ipe_set_active +}; + +/** + * ipe_alloc_policy_tree - allocate the proper subnodes for a policy under + * securityfs. + * @parent: The parent directory that these securityfs files should be created + * under. + * + * Return: + * 0 - OK + * !0 - See securityfs_create_file + */ +static int ipe_alloc_policy_tree(struct dentry *parent) +{ + int rc = 0; + struct dentry *raw = NULL; + struct dentry *delete = NULL; + struct dentry *active = NULL; + struct dentry *content = NULL; + + raw = securityfs_create_file(IPE_FULL_CONTENT, 0644, parent, NULL, + &policy_raw_ops); + if (IS_ERR(raw)) + return PTR_ERR(raw); + + content = securityfs_create_file(IPE_INNER_CONTENT, 0444, parent, + NULL, &policy_content_ops); + if (IS_ERR(raw)) { + rc = PTR_ERR(raw); + goto free_raw; + } + + active = securityfs_create_file(IPE_ACTIVE_POLICY, 0644, parent, NULL, + &policy_active_ops); + if (IS_ERR(active)) { + rc = PTR_ERR(active); + goto free_content; + } + + delete = securityfs_create_file(IPE_DELETE_POLICY, 0644, parent, NULL, + &policy_delete_ops); + if (IS_ERR(delete)) { + rc = PTR_ERR(delete); + goto free_active; + } + + return rc; + +free_active: + securityfs_remove(active); +free_content: + securityfs_remove(content); +free_raw: + securityfs_remove(raw); + + return rc; +} + +/** + * ipe_build_policy_secfs_node: Build a new securityfs node for IPE policies. + * @data: Raw enveloped PKCS#7 data that represents the policy. + * @len: Length of @data. + * + * Return: + * 0 - OK + * -EEXIST - Policy already exists + * -EBADMSG - Invalid policy syntax + * -ENOMEM - Out of memory + */ +int ipe_build_policy_secfs_node(const u8 *data, size_t len) +{ + int rc = 0; + struct dentry *root = NULL; + struct inode *root_i = NULL; + struct crypto_shash *tfm = NULL; + struct ipe_policy_node *node = NULL; + + tfm = crypto_alloc_shash("sha1", 0, 0); + if (IS_ERR(tfm)) { + rc = PTR_ERR(tfm); + goto out; + } + + node = ipe_alloc_policy_node(data, len); + if (IS_ERR(node)) { + rc = PTR_ERR(node); + goto free_hash; + } + + root = securityfs_create_dir(node->parsed->policy_name, + policies_root); + if (IS_ERR(root)) { + rc = PTR_ERR(root); + goto free_private; + } + + root_i = d_inode(root); + + inode_lock(root_i); + root_i->i_private = node; + ipe_audit_policy_load(node->parsed, node->data, node->data_len, tfm); + inode_unlock(root_i); + + rc = ipe_alloc_policy_tree(root); + if (rc) + goto free_secfs; + + crypto_free_shash(tfm); + return rc; + +free_secfs: + securityfs_remove(root); +free_private: + ipe_free_policy_node(node); +free_hash: + crypto_free_shash(tfm); +out: + return rc; +} + +/** + * ipe_new_policy: Entry point of the securityfs node, "ipe/new_policy". + * @f: File representing the securityfs entry. + * @data: Raw enveloped PKCS#7 data that represents the policy. + * @len: Length of @data. + * @offset: Offset for @data. + * + * Return: + * > 0 - OK + * -EEXIST - Policy already exists + * -EBADMSG - Invalid policy syntax + * -ENOMEM - Out of memory + * -EPERM - if a MAC subsystem is enabled, missing CAP_MAC_ADMIN + */ +static ssize_t ipe_new_policy(struct file *f, const char __user *data, + size_t len, loff_t *offset) +{ + ssize_t rc = 0; + u8 *cpy = NULL; + + if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN)) + return -EPERM; + + cpy = memdup_user(data, len); + if (IS_ERR(cpy)) + return PTR_ERR(cpy); + + rc = ipe_build_policy_secfs_node(cpy, len); + + kfree(cpy); + return rc < 0 ? rc : len; +} + +static const struct file_operations new_policy_ops = { + .write = ipe_new_policy +}; + +/** + * ipe_build_secfs_root: Build the root of securityfs for IPE. + * + * Return: + * 0 - OK + * !0 - See securityfs_create_dir and securityfs_create_file + */ +int __init ipe_build_secfs_root(void) +{ + int rc = 0; + struct dentry *new = NULL; + struct dentry *cfg = NULL; + struct dentry *root = NULL; + struct dentry *audit = NULL; + struct dentry *enforce = NULL; + struct dentry *policies = NULL; + + root = securityfs_create_dir(IPE_ROOT, NULL); + if (IS_ERR(root)) { + rc = PTR_ERR(root); + goto out; + } + + new = securityfs_create_file(NEW_POLICY, 0644, root, NULL, + &new_policy_ops); + if (IS_ERR(new)) { + rc = PTR_ERR(new); + goto out_free_root; + } + + policies = securityfs_create_dir(IPE_POLICIES, root); + if (IS_ERR(policies)) { + rc = PTR_ERR(policies); + goto out_free_new; + } + + cfg = securityfs_create_file(IPE_PROPERTY_CFG, 0444, root, NULL, + &prop_cfg_ops); + if (IS_ERR(cfg)) { + rc = PTR_ERR(cfg); + goto out_free_policies; + } + + audit = securityfs_create_file(IPE_SUCCESS_AUDIT, 0644, root, NULL, + &audit_ops); + if (IS_ERR(cfg)) { + rc = PTR_ERR(audit); + goto out_free_cfg; + } + + enforce = ipe_init_enforce_node(root); + if (IS_ERR(enforce)) { + rc = PTR_ERR(audit); + goto out_free_audit; + } + + securityfs_root = root; + new_policy_node = new; + policies_root = policies; + property_cfg_node = cfg; + success_audit_node = audit; + enforce_node = enforce; + + return rc; + +out_free_audit: + securityfs_remove(audit); +out_free_cfg: + securityfs_remove(cfg); +out_free_policies: + securityfs_remove(policies); +out_free_new: + securityfs_remove(new); +out_free_root: + securityfs_remove(root); +out: + return rc; +} + +/** + * ipe_build_secfs_boot_node: Build a policy node for IPE's boot policy. + * + * This differs from the normal policy nodes, as the IPE boot policy is + * read only. + * + * Return: + * 0 - OK + * !0 - See securityfs_create_dir and securityfs_create_file + */ +static int __init ipe_build_secfs_boot_node(void) +{ + int rc = 0; + char *cpy = NULL; + struct inode *root_i = NULL; + struct dentry *root = NULL; + struct dentry *active = NULL; + struct dentry *content = NULL; + struct ipe_policy *parsed = NULL; + struct ipe_policy_node *node = NULL; + + if (!ipe_boot_policy) + return 0; + + node = kzalloc(sizeof(*node), GFP_KERNEL); + if (!node) { + rc = -ENOMEM; + goto out; + } + + cpy = kstrdup(ipe_boot_policy, GFP_KERNEL); + if (!cpy) { + rc = -ENOMEM; + goto out; + } + + parsed = ipe_parse_policy(cpy); + if (IS_ERR(parsed)) { + rc = PTR_ERR(parsed); + goto out_free_policy; + } + + node->content = ipe_boot_policy; + node->content_size = strlen(ipe_boot_policy); + node->parsed = parsed; + + root = securityfs_create_dir(node->parsed->policy_name, + policies_root); + if (IS_ERR(root)) { + rc = PTR_ERR(root); + goto out_free_policy; + } + + content = securityfs_create_file(IPE_INNER_CONTENT, 0444, root, NULL, + &policy_content_ops); + if (IS_ERR(content)) { + rc = PTR_ERR(content); + goto out_free_root; + } + + active = securityfs_create_file(IPE_ACTIVE_POLICY, 0644, root, NULL, + &policy_active_ops); + if (IS_ERR(active)) { + rc = PTR_ERR(active); + goto out_free_content; + } + + root_i = d_inode(root); + + inode_lock(root_i); + root_i->i_private = node; + inode_unlock(root_i); + + boot_policy_node = root; + mutex_lock(&ipe_policy_lock); + rc = ipe_activate_policy(node->parsed); + mutex_unlock(&ipe_policy_lock); + synchronize_rcu(); + + return rc; + +out_free_content: + securityfs_remove(active); +out_free_root: + securityfs_remove(root); +out_free_policy: + ipe_free_policy(parsed); +out: + kfree(cpy); + kfree(node); + return rc; +} + +/** + * ipe_securityfs_init: Initialize IPE's securityfs entries. + * + * This is called after the lsm initialization. + * + * Return: + * 0 - OK + * !0 - Error + */ +int __init ipe_securityfs_init(void) +{ + int rc = 0; + + rc = ipe_build_secfs_root(); + if (rc != 0) + goto err; + + rc = ipe_build_secfs_boot_node(); + if (rc != 0) + panic("IPE failed to initialize the boot policy: %d", rc); + + return rc; +err: + pr_err("failed to initialize secfs: %d", -rc); + return rc; +} + +core_initcall(ipe_securityfs_init); diff --git a/security/ipe/ipe-secfs.h b/security/ipe/ipe-secfs.h new file mode 100644 index 000000000000..147f7801b5e6 --- /dev/null +++ b/security/ipe/ipe-secfs.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ +#include + +#include "ipe-policy.h" + +#ifndef IPE_SECFS_H +#define IPE_SECFS_H + +extern struct mutex ipe_policy_lock; + +int ipe_set_active_policy(const char *id, size_t id_len); + +#endif /* IPE_SECFS_H */ From patchwork Fri Jul 17 23:09:33 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Deven Bowers X-Patchwork-Id: 11671425 X-Patchwork-Delegate: snitzer@redhat.com Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id E23E513A4 for ; Fri, 17 Jul 2020 23:21:37 +0000 (UTC) Received: from us-smtp-1.mimecast.com (us-smtp-delivery-1.mimecast.com [205.139.110.120]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 982C32064C for ; Fri, 17 Jul 2020 23:21:37 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 982C32064C Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=linux.microsoft.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=dm-devel-bounces@redhat.com Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-258-qvWlsdHWMeSQEkTNFKHypQ-1; Fri, 17 Jul 2020 19:21:34 -0400 X-MC-Unique: qvWlsdHWMeSQEkTNFKHypQ-1 Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.phx2.redhat.com [10.5.11.13]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id AAA128064D1; Fri, 17 Jul 2020 23:21:29 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.21]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 8FD9C7BD69; Fri, 17 Jul 2020 23:21:29 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id 6CA2C96258; Fri, 17 Jul 2020 23:21:29 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx06.intmail.prod.int.rdu2.redhat.com [10.11.54.6]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id 06HNGmUF002212 for ; Fri, 17 Jul 2020 19:16:48 -0400 Received: by smtp.corp.redhat.com (Postfix) id 8FAE12156A4B; Fri, 17 Jul 2020 23:16:48 +0000 (UTC) Delivered-To: dm-devel@redhat.com Received: from mimecast-mx02.redhat.com (mimecast06.extmail.prod.ext.rdu2.redhat.com [10.11.55.22]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 8B63D2156A49 for ; Fri, 17 Jul 2020 23:16:46 +0000 (UTC) Received: from us-smtp-1.mimecast.com (us-smtp-2.mimecast.com [205.139.110.61]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 3E1DA185A797 for ; Fri, 17 Jul 2020 23:16:46 +0000 (UTC) Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by relay.mimecast.com with ESMTP id us-mta-122-IyK3660jM46rIY3U5jBdrQ-1; Fri, 17 Jul 2020 19:16:44 -0400 X-MC-Unique: IyK3660jM46rIY3U5jBdrQ-1 Received: from dede-linux-virt.corp.microsoft.com (unknown [131.107.160.54]) by linux.microsoft.com (Postfix) with ESMTPSA id 58F5220B490E; Fri, 17 Jul 2020 16:09:47 -0700 (PDT) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com 58F5220B490E From: Deven Bowers To: agk@redhat.com, axboe@kernel.dk, snitzer@redhat.com, jmorris@namei.org, serge@hallyn.com, zohar@linux.ibm.com, viro@zeniv.linux.org.uk, paul@paul-moore.com, eparis@redhat.com, jannh@google.com, dm-devel@redhat.com, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, linux-fsdevel@vger.kernel.org, linux-block@vger.kernel.org, linux-audit@redhat.com Date: Fri, 17 Jul 2020 16:09:33 -0700 Message-Id: <20200717230941.1190744-5-deven.desai@linux.microsoft.com> In-Reply-To: <20200717230941.1190744-1-deven.desai@linux.microsoft.com> References: <20200717230941.1190744-1-deven.desai@linux.microsoft.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.78 on 10.11.54.6 X-MIME-Autoconverted: from quoted-printable to 8bit by lists01.pubmisc.prod.ext.phx2.redhat.com id 06HNGmUF002212 X-loop: dm-devel@redhat.com Cc: sashal@kernel.org, mdsakib@microsoft.com, corbet@lwn.net, linux-kernel@vger.kernel.org, pasha.tatshin@soleen.com, nramas@linux.microsoft.com, tyhicks@linux.microsoft.com, jaskarankhurana@linux.microsoft.com Subject: [dm-devel] [RFC PATCH v4 04/12] ipe: add property for trust of boot volume X-BeenThere: dm-devel@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: device-mapper development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: dm-devel-bounces@redhat.com Errors-To: dm-devel-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.13 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Add a property for IPE policy to express trust of the first superblock where a file would be evaluated to determine trust. Signed-off-by: Deven Bowers --- security/ipe/Kconfig | 2 + security/ipe/Makefile | 4 ++ security/ipe/ipe-engine.c | 4 ++ security/ipe/ipe-hooks.c | 19 +++++ security/ipe/ipe-hooks.h | 2 + security/ipe/ipe-pin.c | 93 +++++++++++++++++++++++++ security/ipe/ipe-pin.h | 36 ++++++++++ security/ipe/ipe.c | 24 +++++++ security/ipe/properties/Kconfig | 15 ++++ security/ipe/properties/Makefile | 11 +++ security/ipe/properties/boot-verified.c | 82 ++++++++++++++++++++++ security/ipe/properties/prop-entry.h | 20 ++++++ security/ipe/utility.h | 22 ++++++ 13 files changed, 334 insertions(+) create mode 100644 security/ipe/ipe-pin.c create mode 100644 security/ipe/ipe-pin.h create mode 100644 security/ipe/properties/Kconfig create mode 100644 security/ipe/properties/Makefile create mode 100644 security/ipe/properties/boot-verified.c create mode 100644 security/ipe/properties/prop-entry.h create mode 100644 security/ipe/utility.h diff --git a/security/ipe/Kconfig b/security/ipe/Kconfig index 83cf0634043a..f47b7ef94133 100644 --- a/security/ipe/Kconfig +++ b/security/ipe/Kconfig @@ -42,4 +42,6 @@ config SECURITY_IPE_PERMISSIVE_SWITCH If unsure, answer Y. +source "security/ipe/properties/Kconfig" + endif diff --git a/security/ipe/Makefile b/security/ipe/Makefile index 7d6da33dd0c4..7e98982c5035 100644 --- a/security/ipe/Makefile +++ b/security/ipe/Makefile @@ -26,3 +26,7 @@ obj-$(CONFIG_SECURITY_IPE) += \ ipe-secfs.o \ clean-files := ipe-bp.c + +obj-$(CONFIG_IPE_BOOT_PROP) += ipe-pin.o + +obj-$(CONFIG_SECURITY_IPE) += properties/ diff --git a/security/ipe/ipe-engine.c b/security/ipe/ipe-engine.c index c79f422245c6..2eab680587ce 100644 --- a/security/ipe/ipe-engine.c +++ b/security/ipe/ipe-engine.c @@ -9,6 +9,8 @@ #include "ipe-policy.h" #include "ipe-engine.h" #include "ipe-audit.h" +#include "ipe-pin.h" +#include "utility.h" #include #include @@ -205,6 +207,8 @@ int ipe_process_event(const struct file *file, enum ipe_op op, if (IS_ERR(ctx)) goto cleanup; + ipe_pin_superblock(ctx->file); + rc = evaluate(ctx); cleanup: diff --git a/security/ipe/ipe-hooks.c b/security/ipe/ipe-hooks.c index 071c4af23a3d..45efe022be04 100644 --- a/security/ipe/ipe-hooks.c +++ b/security/ipe/ipe-hooks.c @@ -6,6 +6,7 @@ #include "ipe.h" #include "ipe-hooks.h" #include "ipe-engine.h" +#include "ipe-pin.h" #include #include @@ -147,3 +148,21 @@ int ipe_on_kernel_load_data(enum kernel_load_data_id id) ipe_hook_kernel_load); } } + +/** + * ipe_sb_free_security: LSM hook called on sb_free_security. + * @mnt_sb: Super block that is being freed. + * + * IPE does not currently utilize the super block security hook, + * it utilizes this hook to invalidate the saved super block for + * the boot_verified property. + * + * For more information, see the LSM hook, sb_free_security. + * + * Return: + * 0 - OK + */ +void ipe_sb_free_security(struct super_block *mnt_sb) +{ + ipe_invalidate_pinned_sb(mnt_sb); +} diff --git a/security/ipe/ipe-hooks.h b/security/ipe/ipe-hooks.h index 806659b7cdbe..5e46726f2562 100644 --- a/security/ipe/ipe-hooks.h +++ b/security/ipe/ipe-hooks.h @@ -58,4 +58,6 @@ int ipe_on_kernel_read(struct file *file, enum kernel_read_file_id id); int ipe_on_kernel_load_data(enum kernel_load_data_id id); +void ipe_sb_free_security(struct super_block *mnt_sb); + #endif /* IPE_HOOK_H */ diff --git a/security/ipe/ipe-pin.c b/security/ipe/ipe-pin.c new file mode 100644 index 000000000000..a963be8e5321 --- /dev/null +++ b/security/ipe/ipe-pin.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This file has been heavily adapted from the source code of the + * loadpin LSM. The source code for loadpin is co-located in the linux + * tree under security/loadpin/loadpin.c. + * + * Please see loadpin.c for up-to-date information about + * loadpin. + */ + +#include "ipe.h" + +#include +#include +#include +#include +#include +#include +#include + +static DEFINE_SPINLOCK(pinned_sb_spinlock); + +static struct super_block *pinned_sb; + +/** + * ipe_is_from_pinned_sb: Determine if @file originates from the initial + * super block that a file was executed from. + * @file: File to check if it originates from the super block. + * + * Return: + * true - File originates from the initial super block + * false - File does not originate from the initial super block + */ +bool ipe_is_from_pinned_sb(const struct file *file) +{ + bool rv = false; + + spin_lock(&pinned_sb_spinlock); + + /* + * Check if pinned_sb is set: + * NULL == not set -> exit + * ERR == was once set (and has been unmounted) -> exit + * AND check that the pinned sb is the same as the file's. + */ + if (!IS_ERR_OR_NULL(pinned_sb) && + file->f_path.mnt->mnt_sb == pinned_sb) { + rv = true; + goto cleanup; + } + +cleanup: + spin_unlock(&pinned_sb_spinlock); + return rv; +} + +/** + * ipe_pin_superblock: Attempt to save a file's super block address to later + * determine if a file originates from a super block. + * @file: File to source the super block from. + */ +void ipe_pin_superblock(const struct file *file) +{ + spin_lock(&pinned_sb_spinlock); + + /* if set, return */ + if (pinned_sb || !file) + goto cleanup; + + pinned_sb = file->f_path.mnt->mnt_sb; +cleanup: + spin_unlock(&pinned_sb_spinlock); +} + +/** + * ipe_invalidate_pinned_sb: Invalidate the saved super block. + * @mnt_sb: Super block to compare against the saved super block. + * + * This avoids authorizing a file when the super block does not exist anymore. + */ +void ipe_invalidate_pinned_sb(const struct super_block *mnt_sb) +{ + spin_lock(&pinned_sb_spinlock); + + /* + * On pinned sb unload - invalidate the pinned address + * by setting the pinned_sb to ERR_PTR(-EIO) + */ + if (!IS_ERR_OR_NULL(pinned_sb) && mnt_sb == pinned_sb) + pinned_sb = ERR_PTR(-EIO); + + spin_unlock(&pinned_sb_spinlock); +} diff --git a/security/ipe/ipe-pin.h b/security/ipe/ipe-pin.h new file mode 100644 index 000000000000..b707e6253c33 --- /dev/null +++ b/security/ipe/ipe-pin.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ +#ifndef IPE_PIN_H +#define IPE_PIN_H + +#include +#include + +#ifdef CONFIG_IPE_BOOT_PROP + +bool ipe_is_from_pinned_sb(const struct file *file); + +void ipe_pin_superblock(const struct file *file); + +void ipe_invalidate_pinned_sb(const struct super_block *mnt_sb); + +#else /* CONFIG_IPE_BOOT_PROP */ + +static inline bool ipe_is_from_pinned_sb(const struct file *file) +{ + return false; +} + +static inline void ipe_pin_superblock(const struct file *file) +{ +} + +static inline void ipe_invalidate_pinned_sb(const struct super_block *mnt_sb) +{ +} + +#endif /* !CONFIG_IPE_BOOT_PROP */ + +#endif /* IPE_PIN_H */ diff --git a/security/ipe/ipe.c b/security/ipe/ipe.c index a339bca64dd9..4a2623962f8c 100644 --- a/security/ipe/ipe.c +++ b/security/ipe/ipe.c @@ -7,6 +7,7 @@ #include "ipe-policy.h" #include "ipe-hooks.h" #include "ipe-secfs.h" +#include "properties/prop-entry.h" #include #include @@ -22,8 +23,27 @@ static struct security_hook_list ipe_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(kernel_read_file, ipe_on_kernel_read), LSM_HOOK_INIT(kernel_load_data, ipe_on_kernel_load_data), LSM_HOOK_INIT(file_mprotect, ipe_on_mprotect), + LSM_HOOK_INIT(sb_free_security, ipe_sb_free_security), }; +/** + * ipe_load_properties: Call the property entry points for all the IPE modules + * that were selected at kernel build-time. + * + * Return: + * 0 - OK + */ +static int __init ipe_load_properties(void) +{ + int rc = 0; + + rc = ipe_init_bootv(); + if (rc != 0) + return rc; + + return rc; +} + /** * ipe_init: Entry point of IPE. * @@ -41,6 +61,10 @@ static int __init ipe_init(void) { int rc; + rc = ipe_load_properties(); + if (rc != 0) + panic("IPE: properties failed to load"); + pr_info("mode=%s", (ipe_enforce == 1) ? IPE_MODE_ENFORCE : IPE_MODE_PERMISSIVE); diff --git a/security/ipe/properties/Kconfig b/security/ipe/properties/Kconfig new file mode 100644 index 000000000000..75c6c6ff6cd8 --- /dev/null +++ b/security/ipe/properties/Kconfig @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Integrity Policy Enforcement (IPE) configuration +# + +config IPE_BOOT_PROP + bool "Enable trust for boot volume" + help + This option enables the property "boot_verified" in IPE policy. + This property 'pins' the initial superblock when something is + evaluated as an execution. This property will evaluate to true + when the file being evaluated originates from the initial + superblock. + + if unsure, answer N. diff --git a/security/ipe/properties/Makefile b/security/ipe/properties/Makefile new file mode 100644 index 000000000000..e3e7fe17cf58 --- /dev/null +++ b/security/ipe/properties/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (C) Microsoft Corporation. All rights reserved. +# +# Makefile for building the properties that IPE uses +# as part of the kernel tree. +# + +obj-$(CONFIG_SECURITY_IPE) += properties.o + +properties-$(CONFIG_IPE_BOOT_PROP) += boot-verified.o diff --git a/security/ipe/properties/boot-verified.c b/security/ipe/properties/boot-verified.c new file mode 100644 index 000000000000..eb9e6ebe34fa --- /dev/null +++ b/security/ipe/properties/boot-verified.c @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ + +#include "../ipe.h" +#include "../ipe-pin.h" +#include "../ipe-property.h" +#include "../utility.h" + +#include +#include +#include +#include +#include + +#define PROPERTY_NAME "boot_verified" + +static void audit(struct audit_buffer *ab, bool value) +{ + audit_log_format(ab, "%s", (value) ? "TRUE" : "FALSE"); +} + +static inline void audit_rule_value(struct audit_buffer *ab, + const void *value) +{ + audit(ab, (bool)value); +} + +static inline void audit_ctx(struct audit_buffer *ab, + const struct ipe_engine_ctx *ctx) +{ + bool b = has_sb(ctx->file) && ipe_is_from_pinned_sb(ctx->file); + + audit(ab, b); +} + +static bool evaluate(const struct ipe_engine_ctx *ctx, + const void *value) +{ + bool expect = (bool)value; + + if (!ctx->file || !has_sb(ctx->file)) + return false; + + return ipe_is_from_pinned_sb(ctx->file) == expect; +} + +static int parse(const char *val_str, void **value) +{ + if (strcmp("TRUE", val_str) == 0) + *value = (void *)true; + else if (strcmp("FALSE", val_str) == 0) + *value = (void *)false; + else + return -EBADMSG; + + return 0; +} + +static inline int duplicate(const void *src, void **dest) +{ + *dest = (void *)(bool)src; + + return 0; +} + +static const struct ipe_property boot_verified = { + .property_name = PROPERTY_NAME, + .version = 1, + .eval = evaluate, + .rule_audit = audit_rule_value, + .ctx_audit = audit_ctx, + .parse = parse, + .dup = duplicate, + .free_val = NULL, +}; + +int ipe_init_bootv(void) +{ + return ipe_register_property(&boot_verified); +} diff --git a/security/ipe/properties/prop-entry.h b/security/ipe/properties/prop-entry.h new file mode 100644 index 000000000000..f598dd9608b9 --- /dev/null +++ b/security/ipe/properties/prop-entry.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ + +#include + +#ifndef IPE_PROP_ENTRY_H +#define IPE_PROP_ENTRY_H + +#ifndef CONFIG_IPE_BOOT_PROP +static inline int __init ipe_init_bootv(void) +{ + return 0; +} +#else +int __init ipe_init_bootv(void); +#endif /* CONFIG_IPE_BOOT_PROP */ + +#endif /* IPE_PROP_ENTRY_H */ diff --git a/security/ipe/utility.h b/security/ipe/utility.h new file mode 100644 index 000000000000..a13089bb0d8f --- /dev/null +++ b/security/ipe/utility.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ + +#ifndef IPE_UTILITY_H +#define IPE_UTILITY_H + +#include +#include + +static inline bool has_mount(const struct file *file) +{ + return file && file->f_path.mnt; +} + +static inline bool has_sb(const struct file *file) +{ + return has_mount(file) && file->f_path.mnt->mnt_sb; +} + +#endif /* IPE_UTILITY_H */ From patchwork Fri Jul 17 23:09:34 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Deven Bowers X-Patchwork-Id: 11671427 X-Patchwork-Delegate: snitzer@redhat.com Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 057B314E3 for ; Fri, 17 Jul 2020 23:21:41 +0000 (UTC) Received: from us-smtp-delivery-1.mimecast.com (us-smtp-1.mimecast.com [207.211.31.81]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id AAF4E2064C for ; Fri, 17 Jul 2020 23:21:40 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org AAF4E2064C Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=linux.microsoft.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=dm-devel-bounces@redhat.com Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-227-V4PLVK8hPBuOkxS2iopmLA-1; Fri, 17 Jul 2020 19:21:35 -0400 X-MC-Unique: V4PLVK8hPBuOkxS2iopmLA-1 Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.phx2.redhat.com [10.5.11.13]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 576EA100A620; Fri, 17 Jul 2020 23:21:31 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.21]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 3EC467BD67; Fri, 17 Jul 2020 23:21:31 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id 18EB996259; Fri, 17 Jul 2020 23:21:31 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.rdu2.redhat.com [10.11.54.3]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id 06HNGmtI002243 for ; Fri, 17 Jul 2020 19:16:49 -0400 Received: by smtp.corp.redhat.com (Postfix) id DA6ED1054589; Fri, 17 Jul 2020 23:16:48 +0000 (UTC) Delivered-To: dm-devel@redhat.com Received: from mimecast-mx02.redhat.com (mimecast01.extmail.prod.ext.rdu2.redhat.com [10.11.55.17]) by smtp.corp.redhat.com (Postfix) with ESMTPS id D3BF810545B5 for ; Fri, 17 Jul 2020 23:16:46 +0000 (UTC) Received: from us-smtp-1.mimecast.com (us-smtp-1.mimecast.com [207.211.31.81]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 8D230858EE9 for ; Fri, 17 Jul 2020 23:16:46 +0000 (UTC) Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by relay.mimecast.com with ESMTP id us-mta-95-2kqm7tQ2OrKPJgqlHzZUOg-1; Fri, 17 Jul 2020 19:16:42 -0400 X-MC-Unique: 2kqm7tQ2OrKPJgqlHzZUOg-1 Received: from dede-linux-virt.corp.microsoft.com (unknown [131.107.160.54]) by linux.microsoft.com (Postfix) with ESMTPSA id 9961420B490F; Fri, 17 Jul 2020 16:09:47 -0700 (PDT) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com 9961420B490F From: Deven Bowers To: agk@redhat.com, axboe@kernel.dk, snitzer@redhat.com, jmorris@namei.org, serge@hallyn.com, zohar@linux.ibm.com, viro@zeniv.linux.org.uk, paul@paul-moore.com, eparis@redhat.com, jannh@google.com, dm-devel@redhat.com, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, linux-fsdevel@vger.kernel.org, linux-block@vger.kernel.org, linux-audit@redhat.com Date: Fri, 17 Jul 2020 16:09:34 -0700 Message-Id: <20200717230941.1190744-6-deven.desai@linux.microsoft.com> In-Reply-To: <20200717230941.1190744-1-deven.desai@linux.microsoft.com> References: <20200717230941.1190744-1-deven.desai@linux.microsoft.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.78 on 10.11.54.3 X-MIME-Autoconverted: from quoted-printable to 8bit by lists01.pubmisc.prod.ext.phx2.redhat.com id 06HNGmtI002243 X-loop: dm-devel@redhat.com Cc: sashal@kernel.org, mdsakib@microsoft.com, corbet@lwn.net, linux-kernel@vger.kernel.org, pasha.tatshin@soleen.com, nramas@linux.microsoft.com, tyhicks@linux.microsoft.com, jaskarankhurana@linux.microsoft.com Subject: [dm-devel] [RFC PATCH v4 05/12] fs: add security blob and hooks for block_device X-BeenThere: dm-devel@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: device-mapper development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: dm-devel-bounces@redhat.com Errors-To: dm-devel-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.13 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Add a security blob and associated allocation, deallocation and set hooks for a block_device structure. Signed-off-by: Deven Bowers --- fs/block_dev.c | 8 +++++ include/linux/fs.h | 1 + include/linux/lsm_hook_defs.h | 5 +++ include/linux/lsm_hooks.h | 11 +++++++ include/linux/security.h | 22 +++++++++++++ security/security.c | 61 +++++++++++++++++++++++++++++++++++ 6 files changed, 108 insertions(+) diff --git a/fs/block_dev.c b/fs/block_dev.c index 0ae656e022fd..8602dd62c3e2 100644 --- a/fs/block_dev.c +++ b/fs/block_dev.c @@ -34,6 +34,7 @@ #include #include #include +#include #include "internal.h" struct bdev_inode { @@ -768,11 +769,18 @@ static struct inode *bdev_alloc_inode(struct super_block *sb) struct bdev_inode *ei = kmem_cache_alloc(bdev_cachep, GFP_KERNEL); if (!ei) return NULL; + + if (unlikely(security_bdev_alloc(&ei->bdev))) { + kmem_cache_free(bdev_cachep, ei); + return NULL; + } + return &ei->vfs_inode; } static void bdev_free_inode(struct inode *inode) { + security_bdev_free(&BDEV_I(inode)->bdev); kmem_cache_free(bdev_cachep, BDEV_I(inode)); } diff --git a/include/linux/fs.h b/include/linux/fs.h index f5abba86107d..42d7e3ce7712 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -509,6 +509,7 @@ struct block_device { int bd_fsfreeze_count; /* Mutex for freeze */ struct mutex bd_fsfreeze_mutex; + void *security; } __randomize_layout; /* XArray tags, for tagging dirty and writeback pages in the pagecache. */ diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h index af998f93d256..f3c0da0db4e8 100644 --- a/include/linux/lsm_hook_defs.h +++ b/include/linux/lsm_hook_defs.h @@ -391,3 +391,8 @@ LSM_HOOK(void, LSM_RET_VOID, perf_event_free, struct perf_event *event) LSM_HOOK(int, 0, perf_event_read, struct perf_event *event) LSM_HOOK(int, 0, perf_event_write, struct perf_event *event) #endif /* CONFIG_PERF_EVENTS */ + +LSM_HOOK(int, 0, bdev_alloc_security, struct block_device *bdev) +LSM_HOOK(void, LSM_RET_VOID, bdev_free_security, struct block_device *bdev) +LSM_HOOK(int, 0, bdev_setsecurity, struct block_device *bdev, const char *name, + const void *value, size_t size) diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index 95b7c1d32062..8a728b7dd32d 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -1507,6 +1507,16 @@ * * @what: kernel feature being accessed * + * @bdev_alloc_security: + * Initialize the security field inside a block_device structure. + * + * @bdev_free_security: + * Cleanup the security information stored inside a block_device structure. + * + * @bdev_setsecurity: + * Set the security property associated with @name for @bdev with + * value @value. @size indicates the size of the @value in bytes. + * * Security hooks for perf events * * @perf_event_open: @@ -1553,6 +1563,7 @@ struct lsm_blob_sizes { int lbs_ipc; int lbs_msg_msg; int lbs_task; + int lbs_bdev; }; /* diff --git a/include/linux/security.h b/include/linux/security.h index 0a0a03b36a3b..8f83fdc6c65d 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -451,6 +451,11 @@ int security_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen); int security_inode_setsecctx(struct dentry *dentry, void *ctx, u32 ctxlen); int security_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen); int security_locked_down(enum lockdown_reason what); +int security_bdev_alloc(struct block_device *bdev); +void security_bdev_free(struct block_device *bdev); +int security_bdev_setsecurity(struct block_device *bdev, + const char *name, const void *value, + size_t size); #else /* CONFIG_SECURITY */ static inline int call_blocking_lsm_notifier(enum lsm_event event, void *data) @@ -1291,6 +1296,23 @@ static inline int security_locked_down(enum lockdown_reason what) { return 0; } + +static inline int security_bdev_alloc(struct block_device *bdev) +{ + return 0; +} + +static inline void security_bdev_free(struct block_device *bdev) +{ +} + +static inline int security_bdev_setsecurity(struct block_device *bdev, + const char *name, + const void *value, size_t size) +{ + return 0; +} + #endif /* CONFIG_SECURITY */ #if defined(CONFIG_SECURITY) && defined(CONFIG_WATCH_QUEUE) diff --git a/security/security.c b/security/security.c index 70a7ad357bc6..8e61b7e6adfc 100644 --- a/security/security.c +++ b/security/security.c @@ -28,6 +28,7 @@ #include #include #include +#include #define MAX_LSM_EVM_XATTR 2 @@ -202,6 +203,7 @@ static void __init lsm_set_blob_sizes(struct lsm_blob_sizes *needed) lsm_set_blob_size(&needed->lbs_ipc, &blob_sizes.lbs_ipc); lsm_set_blob_size(&needed->lbs_msg_msg, &blob_sizes.lbs_msg_msg); lsm_set_blob_size(&needed->lbs_task, &blob_sizes.lbs_task); + lsm_set_blob_size(&needed->lbs_bdev, &blob_sizes.lbs_bdev); } /* Prepare LSM for initialization. */ @@ -337,6 +339,7 @@ static void __init ordered_lsm_init(void) init_debug("ipc blob size = %d\n", blob_sizes.lbs_ipc); init_debug("msg_msg blob size = %d\n", blob_sizes.lbs_msg_msg); init_debug("task blob size = %d\n", blob_sizes.lbs_task); + init_debug("bdev blob size = %d\n", blob_sizes.lbs_bdev); /* * Create any kmem_caches needed for blobs @@ -654,6 +657,28 @@ static int lsm_msg_msg_alloc(struct msg_msg *mp) return 0; } +/** + * lsm_bdev_alloc - allocate a composite block_device blob + * @bdev: the block_device that needs a blob + * + * Allocate the block_device blob for all the modules + * + * Returns 0, or -ENOMEM if memory can't be allocated. + */ +static int lsm_bdev_alloc(struct block_device *bdev) +{ + if (blob_sizes.lbs_bdev == 0) { + bdev->security = NULL; + return 0; + } + + bdev->security = kzalloc(blob_sizes.lbs_bdev, GFP_KERNEL); + if (!bdev->security) + return -ENOMEM; + + return 0; +} + /** * lsm_early_task - during initialization allocate a composite task blob * @task: the task that needs a blob @@ -2516,6 +2541,42 @@ int security_locked_down(enum lockdown_reason what) } EXPORT_SYMBOL(security_locked_down); +int security_bdev_alloc(struct block_device *bdev) +{ + int rc = 0; + + rc = lsm_bdev_alloc(bdev); + if (unlikely(rc)) + return rc; + + rc = call_int_hook(bdev_alloc_security, 0, bdev); + if (unlikely(rc)) + security_bdev_free(bdev); + + return 0; +} +EXPORT_SYMBOL(security_bdev_alloc); + +void security_bdev_free(struct block_device *bdev) +{ + if (!bdev->security) + return; + + call_void_hook(bdev_free_security, bdev); + + kfree(bdev->security); + bdev->security = NULL; +} +EXPORT_SYMBOL(security_bdev_free); + +int security_bdev_setsecurity(struct block_device *bdev, + const char *name, const void *value, + size_t size) +{ + return call_int_hook(bdev_setsecurity, 0, bdev, name, value, size); +} +EXPORT_SYMBOL(security_bdev_setsecurity); + #ifdef CONFIG_PERF_EVENTS int security_perf_event_open(struct perf_event_attr *attr, int type) { From patchwork Fri Jul 17 23:09:35 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Deven Bowers X-Patchwork-Id: 11671417 X-Patchwork-Delegate: snitzer@redhat.com Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id D425113A4 for ; Fri, 17 Jul 2020 23:21:28 +0000 (UTC) Received: from us-smtp-1.mimecast.com (us-smtp-delivery-1.mimecast.com [205.139.110.120]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 893402064C for ; Fri, 17 Jul 2020 23:21:28 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 893402064C Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=linux.microsoft.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=dm-devel-bounces@redhat.com Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-202-jFND5f4eMqWfG3hi3RjavA-1; Fri, 17 Jul 2020 19:21:25 -0400 X-MC-Unique: jFND5f4eMqWfG3hi3RjavA-1 Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.phx2.redhat.com [10.5.11.13]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id E430B18C63C0; Fri, 17 Jul 2020 23:21:20 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.20]) by smtp.corp.redhat.com (Postfix) with ESMTPS id C817D7BD77; Fri, 17 Jul 2020 23:21:20 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id A0F8A1800B70; Fri, 17 Jul 2020 23:21:20 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx05.intmail.prod.int.rdu2.redhat.com [10.11.54.5]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id 06HNGmoW002215 for ; Fri, 17 Jul 2020 19:16:48 -0400 Received: by smtp.corp.redhat.com (Postfix) id A5A5A11515E; Fri, 17 Jul 2020 23:16:48 +0000 (UTC) Delivered-To: dm-devel@redhat.com Received: from mimecast-mx02.redhat.com (mimecast06.extmail.prod.ext.rdu2.redhat.com [10.11.55.22]) by smtp.corp.redhat.com (Postfix) with ESMTPS id A0B1411516A for ; Fri, 17 Jul 2020 23:16:46 +0000 (UTC) Received: from us-smtp-1.mimecast.com (us-smtp-delivery-1.mimecast.com [207.211.31.120]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 9C9631832D2B for ; Fri, 17 Jul 2020 23:16:46 +0000 (UTC) Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by relay.mimecast.com with ESMTP id us-mta-295-IlS0pIiJNTecKrJy_frVtQ-1; Fri, 17 Jul 2020 19:16:42 -0400 X-MC-Unique: IlS0pIiJNTecKrJy_frVtQ-1 Received: from dede-linux-virt.corp.microsoft.com (unknown [131.107.160.54]) by linux.microsoft.com (Postfix) with ESMTPSA id DC2CA20B4911; Fri, 17 Jul 2020 16:09:47 -0700 (PDT) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com DC2CA20B4911 From: Deven Bowers To: agk@redhat.com, axboe@kernel.dk, snitzer@redhat.com, jmorris@namei.org, serge@hallyn.com, zohar@linux.ibm.com, viro@zeniv.linux.org.uk, paul@paul-moore.com, eparis@redhat.com, jannh@google.com, dm-devel@redhat.com, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, linux-fsdevel@vger.kernel.org, linux-block@vger.kernel.org, linux-audit@redhat.com Date: Fri, 17 Jul 2020 16:09:35 -0700 Message-Id: <20200717230941.1190744-7-deven.desai@linux.microsoft.com> In-Reply-To: <20200717230941.1190744-1-deven.desai@linux.microsoft.com> References: <20200717230941.1190744-1-deven.desai@linux.microsoft.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.11.54.5 X-MIME-Autoconverted: from quoted-printable to 8bit by lists01.pubmisc.prod.ext.phx2.redhat.com id 06HNGmoW002215 X-loop: dm-devel@redhat.com Cc: sashal@kernel.org, mdsakib@microsoft.com, corbet@lwn.net, linux-kernel@vger.kernel.org, pasha.tatshin@soleen.com, nramas@linux.microsoft.com, tyhicks@linux.microsoft.com, jaskarankhurana@linux.microsoft.com Subject: [dm-devel] [RFC PATCH v4 06/12] dm-verity: move signature check after tree validation X-BeenThere: dm-devel@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: device-mapper development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: dm-devel-bounces@redhat.com Errors-To: dm-devel-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.13 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com The CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG introduced by Jaskaran was intended to be used to allow an LSM to enforce verifications for all dm-verity volumes. However, with it's current implementation, this signature verification occurs after the merkel-tree is validated, as a result the signature can pass initial verification by passing a matching root-hash and signature. This results in an unreadable block_device, but that has passed signature validation (and subsequently, would be marked as verified). This change moves the signature verification to after the merkel-tree has finished validation. Signed-off-by: Deven Bowers --- drivers/md/dm-verity-target.c | 42 ++++----- drivers/md/dm-verity-verify-sig.c | 140 ++++++++++++++++++++++-------- drivers/md/dm-verity-verify-sig.h | 20 ++--- drivers/md/dm-verity.h | 2 +- 4 files changed, 132 insertions(+), 72 deletions(-) diff --git a/drivers/md/dm-verity-target.c b/drivers/md/dm-verity-target.c index eec9f252e935..c507f3a4e237 100644 --- a/drivers/md/dm-verity-target.c +++ b/drivers/md/dm-verity-target.c @@ -471,9 +471,9 @@ static int verity_verify_io(struct dm_verity_io *io) struct bvec_iter start; unsigned b; struct crypto_wait wait; + int r; for (b = 0; b < io->n_blocks; b++) { - int r; sector_t cur_block = io->block + b; struct ahash_request *req = verity_io_hash_req(v, io); @@ -530,6 +530,16 @@ static int verity_verify_io(struct dm_verity_io *io) return -EIO; } + /* + * At this point, the merkel tree has finished validating. + * if signature was specified, validate the signature here. + */ + r = verity_verify_root_hash(v); + if (r < 0) { + DMERR_LIMIT("signature mismatch"); + return r; + } + return 0; } @@ -728,7 +738,7 @@ static void verity_status(struct dm_target *ti, status_type_t type, args++; if (v->validated_blocks) args++; - if (v->signature_key_desc) + if (v->sig) args += DM_VERITY_ROOT_HASH_VERIFICATION_OPTS; if (!args) return; @@ -751,9 +761,9 @@ static void verity_status(struct dm_target *ti, status_type_t type, if (v->validated_blocks) DMEMIT(" " DM_VERITY_OPT_AT_MOST_ONCE); sz = verity_fec_status_table(v, sz, result, maxlen); - if (v->signature_key_desc) + if (v->sig) DMEMIT(" " DM_VERITY_ROOT_HASH_VERIFICATION_OPT_SIG_KEY - " %s", v->signature_key_desc); + " %s", v->sig->signature_key_desc); break; } } @@ -819,7 +829,7 @@ static void verity_dtr(struct dm_target *ti) verity_fec_dtr(v); - kfree(v->signature_key_desc); + verity_verify_dtr(v); kfree(v); } @@ -876,8 +886,7 @@ static int verity_alloc_zero_digest(struct dm_verity *v) return r; } -static int verity_parse_opt_args(struct dm_arg_set *as, struct dm_verity *v, - struct dm_verity_sig_opts *verify_args) +static int verity_parse_opt_args(struct dm_arg_set *as, struct dm_verity *v) { int r; unsigned argc; @@ -927,9 +936,7 @@ static int verity_parse_opt_args(struct dm_arg_set *as, struct dm_verity *v, return r; continue; } else if (verity_verify_is_sig_opt_arg(arg_name)) { - r = verity_verify_sig_parse_opt_args(as, v, - verify_args, - &argc, arg_name); + r = verity_verify_sig_parse_opt_args(as, v, &argc); if (r) return r; continue; @@ -960,7 +967,6 @@ static int verity_parse_opt_args(struct dm_arg_set *as, struct dm_verity *v, static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv) { struct dm_verity *v; - struct dm_verity_sig_opts verify_args = {0}; struct dm_arg_set as; unsigned int num; unsigned long long num_ll; @@ -1128,20 +1134,11 @@ static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv) as.argc = argc; as.argv = argv; - r = verity_parse_opt_args(&as, v, &verify_args); + r = verity_parse_opt_args(&as, v); if (r < 0) goto bad; } - /* Root hash signature is a optional parameter*/ - r = verity_verify_root_hash(root_hash_digest_to_validate, - strlen(root_hash_digest_to_validate), - verify_args.sig, - verify_args.sig_size); - if (r < 0) { - ti->error = "Root hash verification failed"; - goto bad; - } v->hash_per_block_bits = __fls((1 << v->hash_dev_block_bits) / v->digest_size); @@ -1207,13 +1204,10 @@ static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv) ti->per_io_data_size = roundup(ti->per_io_data_size, __alignof__(struct dm_verity_io)); - verity_verify_sig_opts_cleanup(&verify_args); - return 0; bad: - verity_verify_sig_opts_cleanup(&verify_args); verity_dtr(ti); return r; diff --git a/drivers/md/dm-verity-verify-sig.c b/drivers/md/dm-verity-verify-sig.c index 614e43db93aa..27dac8aa2e5a 100644 --- a/drivers/md/dm-verity-verify-sig.c +++ b/drivers/md/dm-verity-verify-sig.c @@ -22,6 +22,16 @@ MODULE_PARM_DESC(require_signatures, #define DM_VERITY_IS_SIG_FORCE_ENABLED() \ (require_signatures != false) +static void destroy_verity_sig(struct dm_verity_sig *sig_info) +{ + if (!sig_info) + return; + + kfree(sig_info->sig); + kfree(sig_info->signature_key_desc); + kfree(sig_info); +} + bool verity_verify_is_sig_opt_arg(const char *arg_name) { return (!strcasecmp(arg_name, @@ -29,7 +39,7 @@ bool verity_verify_is_sig_opt_arg(const char *arg_name) } static int verity_verify_get_sig_from_key(const char *key_desc, - struct dm_verity_sig_opts *sig_opts) + struct dm_verity_sig *sig_info) { struct key *key; const struct user_key_payload *ukp; @@ -48,14 +58,14 @@ static int verity_verify_get_sig_from_key(const char *key_desc, goto end; } - sig_opts->sig = kmalloc(ukp->datalen, GFP_KERNEL); - if (!sig_opts->sig) { + sig_info->sig = kmalloc(ukp->datalen, GFP_KERNEL); + if (!sig_info->sig) { ret = -ENOMEM; goto end; } - sig_opts->sig_size = ukp->datalen; + sig_info->sig_size = ukp->datalen; - memcpy(sig_opts->sig, ukp->data, sig_opts->sig_size); + memcpy(sig_info->sig, ukp->data, sig_info->sig_size); end: up_read(&key->sem); @@ -64,70 +74,128 @@ static int verity_verify_get_sig_from_key(const char *key_desc, return ret; } +/** + * Parse any signature verification arguments. + * This function will populate v->sig, it is the caller's + * responsibility to free this structure via verity_verify_dtr + * + * @as: argument set passed in to parse + * @v: verity context structure. Should have a NULL v->sig member. + * @argc: current argument number + */ int verity_verify_sig_parse_opt_args(struct dm_arg_set *as, struct dm_verity *v, - struct dm_verity_sig_opts *sig_opts, - unsigned int *argc, - const char *arg_name) + unsigned int *argc) { struct dm_target *ti = v->ti; + struct dm_verity_sig *sig_info = NULL; int ret = 0; const char *sig_key = NULL; if (!*argc) { ti->error = DM_VERITY_VERIFY_ERR("Signature key not specified"); - return -EINVAL; + ret = -EINVAL; + goto cleanup; + } + + sig_info = kzalloc(sizeof(*sig_info), GFP_KERNEL); + if (!sig_info) { + ret = -ENOMEM; + goto cleanup; } sig_key = dm_shift_arg(as); (*argc)--; - ret = verity_verify_get_sig_from_key(sig_key, sig_opts); - if (ret < 0) + ret = verity_verify_get_sig_from_key(sig_key, sig_info); + if (ret < 0) { ti->error = DM_VERITY_VERIFY_ERR("Invalid key specified"); + goto cleanup; + } - v->signature_key_desc = kstrdup(sig_key, GFP_KERNEL); - if (!v->signature_key_desc) - return -ENOMEM; + sig_info->signature_key_desc = kstrdup(sig_key, GFP_KERNEL); + if (!sig_info->signature_key_desc) { + ret = -ENOMEM; + goto cleanup; + } + v->sig = sig_info; + sig_info = NULL; +cleanup: + if (sig_info) + destroy_verity_sig(sig_info); return ret; } -/* +/** * verify_verify_roothash - Verify the root hash of the verity hash device * using builtin trusted keys. * - * @root_hash: For verity, the roothash/data to be verified. - * @root_hash_len: Size of the roothash/data to be verified. - * @sig_data: The trusted signature that verifies the roothash/data. - * @sig_len: Size of the signature. + * @v: dm_verity structure containing all context for the dm_verity + * operation. * */ -int verity_verify_root_hash(const void *root_hash, size_t root_hash_len, - const void *sig_data, size_t sig_len) +int verity_verify_root_hash(const struct dm_verity *v) { - int ret; + int ret = 0; + char *root_hash = NULL; + size_t root_hash_size = 0; + struct dm_verity_sig *sig_target = NULL; + + if (!v || !v->ti || !v->root_digest || v->digest_size == 0) { + ret = -EINVAL; + goto cleanup; + } + + sig_target = v->sig; + + if (!sig_target || !sig_target->sig || sig_target->sig_size == 0) { + if (DM_VERITY_IS_SIG_FORCE_ENABLED()) { + ret = -ENOKEY; + goto cleanup; + } else { + goto cleanup; + } + } - if (!root_hash || root_hash_len == 0) - return -EINVAL; + /* + * If signature has passed validation once, assume + * that future signatures will pass. + */ + if (sig_target->passed) + goto cleanup; - if (!sig_data || sig_len == 0) { - if (DM_VERITY_IS_SIG_FORCE_ENABLED()) - return -ENOKEY; - else - return 0; + root_hash_size = v->digest_size * 2; + root_hash = kzalloc(root_hash_size, GFP_KERNEL); + if (!root_hash) { + ret = -ENOMEM; + goto cleanup; } - ret = verify_pkcs7_signature(root_hash, root_hash_len, sig_data, - sig_len, NULL, VERIFYING_UNSPECIFIED_SIGNATURE, - NULL, NULL); + bin2hex(root_hash, v->root_digest, v->digest_size); + + ret = verify_pkcs7_signature(root_hash, root_hash_size, v->sig->sig, + v->sig->sig_size, NULL, + VERIFYING_UNSPECIFIED_SIGNATURE, NULL, + NULL); + if (ret != 0) + goto cleanup; + sig_target->passed = true; +cleanup: + kfree(root_hash); return ret; } -void verity_verify_sig_opts_cleanup(struct dm_verity_sig_opts *sig_opts) +/** + * Performs destruction / cleanup of a valid dm_verity_sig struct + * + * @v: dm_verity structure containing the dm_verity_sig struct to + * be freed. + */ + +void verity_verify_dtr(struct dm_verity *v) { - kfree(sig_opts->sig); - sig_opts->sig = NULL; - sig_opts->sig_size = 0; + destroy_verity_sig(v->sig); + v->sig = NULL; } diff --git a/drivers/md/dm-verity-verify-sig.h b/drivers/md/dm-verity-verify-sig.h index 19b1547aa741..81158d7702a3 100644 --- a/drivers/md/dm-verity-verify-sig.h +++ b/drivers/md/dm-verity-verify-sig.h @@ -11,31 +11,30 @@ #define DM_VERITY_ROOT_HASH_VERIFICATION "DM Verity Sig Verification" #define DM_VERITY_ROOT_HASH_VERIFICATION_OPT_SIG_KEY "root_hash_sig_key_desc" -struct dm_verity_sig_opts { +struct dm_verity_sig { + char *signature_key_desc; unsigned int sig_size; u8 *sig; + bool passed; }; #ifdef CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG #define DM_VERITY_ROOT_HASH_VERIFICATION_OPTS 2 -int verity_verify_root_hash(const void *data, size_t data_len, - const void *sig_data, size_t sig_len); +int verity_verify_root_hash(const struct dm_verity *v); bool verity_verify_is_sig_opt_arg(const char *arg_name); int verity_verify_sig_parse_opt_args(struct dm_arg_set *as, struct dm_verity *v, - struct dm_verity_sig_opts *sig_opts, - unsigned int *argc, const char *arg_name); + unsigned int *argc); -void verity_verify_sig_opts_cleanup(struct dm_verity_sig_opts *sig_opts); +void verity_verify_dtr(struct dm_verity *v); #else #define DM_VERITY_ROOT_HASH_VERIFICATION_OPTS 0 -int verity_verify_root_hash(const void *data, size_t data_len, - const void *sig_data, size_t sig_len) +int verity_verify_root_hash(const struct dm_verity *v) { return 0; } @@ -46,13 +45,12 @@ bool verity_verify_is_sig_opt_arg(const char *arg_name) } int verity_verify_sig_parse_opt_args(struct dm_arg_set *as, struct dm_verity *v, - struct dm_verity_sig_opts *sig_opts, - unsigned int *argc, const char *arg_name) + unsigned int *argc) { return -EINVAL; } -void verity_verify_sig_opts_cleanup(struct dm_verity_sig_opts *sig_opts) +void verity_verify_dtr(struct dm_verity *v) { } diff --git a/drivers/md/dm-verity.h b/drivers/md/dm-verity.h index 641b9e3a399b..995c495decad 100644 --- a/drivers/md/dm-verity.h +++ b/drivers/md/dm-verity.h @@ -64,7 +64,7 @@ struct dm_verity { struct dm_verity_fec *fec; /* forward error correction */ unsigned long *validated_blocks; /* bitset blocks validated */ - char *signature_key_desc; /* signature keyring reference */ + struct dm_verity_sig *sig; /* signature verification */ }; struct dm_verity_io { From patchwork Fri Jul 17 23:09:36 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Deven Bowers X-Patchwork-Id: 11671405 X-Patchwork-Delegate: snitzer@redhat.com Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 9DB9414E3 for ; Fri, 17 Jul 2020 23:20:59 +0000 (UTC) Received: from us-smtp-1.mimecast.com (us-smtp-delivery-1.mimecast.com [207.211.31.120]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 4DE822064C for ; Fri, 17 Jul 2020 23:20:59 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 4DE822064C Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=linux.microsoft.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=dm-devel-bounces@redhat.com Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-505-v3lmV6NzP56SjzgLpASR6Q-1; Fri, 17 Jul 2020 19:20:54 -0400 X-MC-Unique: v3lmV6NzP56SjzgLpASR6Q-1 Received: from smtp.corp.redhat.com (int-mx06.intmail.prod.int.phx2.redhat.com [10.5.11.16]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 7C43518C63C0; Fri, 17 Jul 2020 23:20:50 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.21]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 2FD625C240; Fri, 17 Jul 2020 23:20:50 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id D87D196255; Fri, 17 Jul 2020 23:20:49 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx05.intmail.prod.int.rdu2.redhat.com [10.11.54.5]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id 06HNGmJW002220 for ; Fri, 17 Jul 2020 19:16:48 -0400 Received: by smtp.corp.redhat.com (Postfix) id A5C5C115171; Fri, 17 Jul 2020 23:16:48 +0000 (UTC) Delivered-To: dm-devel@redhat.com Received: from mimecast-mx02.redhat.com (mimecast04.extmail.prod.ext.rdu2.redhat.com [10.11.55.20]) by smtp.corp.redhat.com (Postfix) with ESMTPS id A0848114D18 for ; Fri, 17 Jul 2020 23:16:45 +0000 (UTC) Received: from us-smtp-1.mimecast.com (us-smtp-1.mimecast.com [205.139.110.61]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 9C6711012440 for ; Fri, 17 Jul 2020 23:16:45 +0000 (UTC) Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by relay.mimecast.com with ESMTP id us-mta-352-iD_UX1LbO0i1jtY8edAoBw-1; Fri, 17 Jul 2020 19:16:43 -0400 X-MC-Unique: iD_UX1LbO0i1jtY8edAoBw-1 Received: from dede-linux-virt.corp.microsoft.com (unknown [131.107.160.54]) by linux.microsoft.com (Postfix) with ESMTPSA id 2885C20B4913; Fri, 17 Jul 2020 16:09:48 -0700 (PDT) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com 2885C20B4913 From: Deven Bowers To: agk@redhat.com, axboe@kernel.dk, snitzer@redhat.com, jmorris@namei.org, serge@hallyn.com, zohar@linux.ibm.com, viro@zeniv.linux.org.uk, paul@paul-moore.com, eparis@redhat.com, jannh@google.com, dm-devel@redhat.com, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, linux-fsdevel@vger.kernel.org, linux-block@vger.kernel.org, linux-audit@redhat.com Date: Fri, 17 Jul 2020 16:09:36 -0700 Message-Id: <20200717230941.1190744-8-deven.desai@linux.microsoft.com> In-Reply-To: <20200717230941.1190744-1-deven.desai@linux.microsoft.com> References: <20200717230941.1190744-1-deven.desai@linux.microsoft.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.11.54.5 X-MIME-Autoconverted: from quoted-printable to 8bit by lists01.pubmisc.prod.ext.phx2.redhat.com id 06HNGmJW002220 X-loop: dm-devel@redhat.com Cc: sashal@kernel.org, mdsakib@microsoft.com, corbet@lwn.net, linux-kernel@vger.kernel.org, pasha.tatshin@soleen.com, nramas@linux.microsoft.com, tyhicks@linux.microsoft.com, jaskarankhurana@linux.microsoft.com Subject: [dm-devel] [RFC PATCH v4 07/12] dm-verity: add bdev_setsecurity hook for dm-verity signature X-BeenThere: dm-devel@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: device-mapper development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: dm-devel-bounces@redhat.com Errors-To: dm-devel-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.16 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Add a security hook call to set a security property of a block_device in dm-verity with the results of a verified, signed root-hash. Signed-off-by: Deven Bowers --- drivers/md/dm-verity-verify-sig.c | 7 +++++++ include/linux/device-mapper.h | 2 ++ 2 files changed, 9 insertions(+) diff --git a/drivers/md/dm-verity-verify-sig.c b/drivers/md/dm-verity-verify-sig.c index 27dac8aa2e5a..242e2421d3c8 100644 --- a/drivers/md/dm-verity-verify-sig.c +++ b/drivers/md/dm-verity-verify-sig.c @@ -8,7 +8,10 @@ #include #include #include +#include +#include #include +#include "dm-core.h" #include "dm-verity.h" #include "dm-verity-verify-sig.h" @@ -182,6 +185,10 @@ int verity_verify_root_hash(const struct dm_verity *v) goto cleanup; sig_target->passed = true; + + ret = security_bdev_setsecurity(dm_table_get_md(v->ti->table)->bdev, + DM_VERITY_SIGNATURE_SEC_NAME, + v->sig->sig, v->sig->sig_size); cleanup: kfree(root_hash); return ret; diff --git a/include/linux/device-mapper.h b/include/linux/device-mapper.h index 8750f2dc5613..02be0be21d38 100644 --- a/include/linux/device-mapper.h +++ b/include/linux/device-mapper.h @@ -624,4 +624,6 @@ static inline unsigned long to_bytes(sector_t n) return (n << SECTOR_SHIFT); } +#define DM_VERITY_SIGNATURE_SEC_NAME DM_NAME ".verity-sig" + #endif /* _LINUX_DEVICE_MAPPER_H */ From patchwork Fri Jul 17 23:09:37 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Deven Bowers X-Patchwork-Id: 11671407 X-Patchwork-Delegate: snitzer@redhat.com Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id AC2E913A4 for ; Fri, 17 Jul 2020 23:21:02 +0000 (UTC) Received: from us-smtp-1.mimecast.com (us-smtp-delivery-1.mimecast.com [205.139.110.120]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 6088D2064C for ; Fri, 17 Jul 2020 23:21:02 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 6088D2064C Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=linux.microsoft.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=dm-devel-bounces@redhat.com Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-104-WnpF5hpqMZ-FFnunU8qs9Q-1; Fri, 17 Jul 2020 19:20:59 -0400 X-MC-Unique: WnpF5hpqMZ-FFnunU8qs9Q-1 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.14]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id BDFB4100A614; Fri, 17 Jul 2020 23:20:54 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.20]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 9FF491DA; Fri, 17 Jul 2020 23:20:54 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id 7A2671809554; Fri, 17 Jul 2020 23:20:54 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx06.intmail.prod.int.rdu2.redhat.com [10.11.54.6]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id 06HNGmeF002221 for ; Fri, 17 Jul 2020 19:16:48 -0400 Received: by smtp.corp.redhat.com (Postfix) id A74332156A49; Fri, 17 Jul 2020 23:16:48 +0000 (UTC) Delivered-To: dm-devel@redhat.com Received: from mimecast-mx02.redhat.com (mimecast04.extmail.prod.ext.rdu2.redhat.com [10.11.55.20]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 9EA6B2156A2E for ; Fri, 17 Jul 2020 23:16:48 +0000 (UTC) Received: from us-smtp-1.mimecast.com (us-smtp-delivery-1.mimecast.com [205.139.110.120]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 807BF1012443 for ; Fri, 17 Jul 2020 23:16:48 +0000 (UTC) Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by relay.mimecast.com with ESMTP id us-mta-220-49P71Lj-N5CTTTCvMfQ5uw-1; Fri, 17 Jul 2020 19:16:44 -0400 X-MC-Unique: 49P71Lj-N5CTTTCvMfQ5uw-1 Received: from dede-linux-virt.corp.microsoft.com (unknown [131.107.160.54]) by linux.microsoft.com (Postfix) with ESMTPSA id 6A1F020B4914; Fri, 17 Jul 2020 16:09:48 -0700 (PDT) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com 6A1F020B4914 From: Deven Bowers To: agk@redhat.com, axboe@kernel.dk, snitzer@redhat.com, jmorris@namei.org, serge@hallyn.com, zohar@linux.ibm.com, viro@zeniv.linux.org.uk, paul@paul-moore.com, eparis@redhat.com, jannh@google.com, dm-devel@redhat.com, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, linux-fsdevel@vger.kernel.org, linux-block@vger.kernel.org, linux-audit@redhat.com Date: Fri, 17 Jul 2020 16:09:37 -0700 Message-Id: <20200717230941.1190744-9-deven.desai@linux.microsoft.com> In-Reply-To: <20200717230941.1190744-1-deven.desai@linux.microsoft.com> References: <20200717230941.1190744-1-deven.desai@linux.microsoft.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.78 on 10.11.54.6 X-MIME-Autoconverted: from quoted-printable to 8bit by lists01.pubmisc.prod.ext.phx2.redhat.com id 06HNGmeF002221 X-loop: dm-devel@redhat.com Cc: sashal@kernel.org, mdsakib@microsoft.com, corbet@lwn.net, linux-kernel@vger.kernel.org, pasha.tatshin@soleen.com, nramas@linux.microsoft.com, tyhicks@linux.microsoft.com, jaskarankhurana@linux.microsoft.com Subject: [dm-devel] [RFC PATCH v4 08/12] ipe: add property for signed dmverity volumes X-BeenThere: dm-devel@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: device-mapper development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: dm-devel-bounces@redhat.com Errors-To: dm-devel-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.14 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=dm-devel-bounces@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Allow IPE to leverage the stacked security blob infrastructure, and enlighten IPE to the block_device security blob. This allows IPE to have a property to express rules around a device-mapper verity volume whose root-hash has been signed, and the signature has been verified against the system keyring. This is property is also added in this patch. Signed-off-by: Deven Bowers --- security/ipe/Kconfig | 2 +- security/ipe/Makefile | 1 + security/ipe/ipe-blobs.c | 82 ++++++++++++++++++++ security/ipe/ipe-blobs.h | 18 +++++ security/ipe/ipe-engine.c | 4 + security/ipe/ipe-engine.h | 9 +++ security/ipe/ipe-hooks.c | 1 + security/ipe/ipe-hooks.h | 7 ++ security/ipe/ipe.c | 18 +++++ security/ipe/ipe.h | 2 + security/ipe/properties/Kconfig | 10 +++ security/ipe/properties/Makefile | 1 + security/ipe/properties/dmverity-signature.c | 82 ++++++++++++++++++++ security/ipe/properties/prop-entry.h | 9 +++ security/ipe/utility.h | 10 +++ 15 files changed, 255 insertions(+), 1 deletion(-) create mode 100644 security/ipe/ipe-blobs.c create mode 100644 security/ipe/ipe-blobs.h create mode 100644 security/ipe/properties/dmverity-signature.c diff --git a/security/ipe/Kconfig b/security/ipe/Kconfig index f47b7ef94133..9b5e8fc77a96 100644 --- a/security/ipe/Kconfig +++ b/security/ipe/Kconfig @@ -5,7 +5,7 @@ menuconfig SECURITY_IPE bool "Integrity Policy Enforcement (IPE)" - depends on SECURITY && AUDIT + depends on SECURITY && AUDIT && BLOCK select SYSTEM_DATA_VERIFICATION select SECURITYFS select CRYPTO_SHA1 diff --git a/security/ipe/Makefile b/security/ipe/Makefile index 7e98982c5035..98a2245b6956 100644 --- a/security/ipe/Makefile +++ b/security/ipe/Makefile @@ -24,6 +24,7 @@ obj-$(CONFIG_SECURITY_IPE) += \ ipe-property.o \ ipe-hooks.o \ ipe-secfs.o \ + ipe-blobs.o \ clean-files := ipe-bp.c diff --git a/security/ipe/ipe-blobs.c b/security/ipe/ipe-blobs.c new file mode 100644 index 000000000000..03e916d13ac1 --- /dev/null +++ b/security/ipe/ipe-blobs.c @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ + +#include "ipe.h" +#include "ipe-engine.h" +#include "ipe-blobs.h" + +#include +#include +#include + +/** + * ipe_bdev_alloc_security: Performs the initialization of IPE's security blob. + * @bdev: The block device to source the security blob from. + * + * The allocation is performed earlier by the LSM infrastructure, + * (on behalf of all LSMs) in lsm_alloc_bdev. At the moment, IPE uses + * this time to zero out the region of memory reserved for IPE. + * + * Return: + * 0 - OK + */ +int ipe_bdev_alloc_security(struct block_device *bdev) +{ + struct ipe_bdev_blob *bdev_sec = ipe_bdev(bdev); + + memset(bdev_sec, 0x0, sizeof(*bdev_sec)); + + return 0; +} + +/** + * ipe_bdev_free_security: Frees all fields of IPE's block dev security blob. + * @bdev: The block device to source the security blob from. + * + * The deallocation of the blob itself is performed later by the LSM + * infrastructure, (on behalf of all LSMs) in lsm_free_bdev. + * + * Pointers allocated by the bdev_setsecurity hook and alloc_security + * hook need to be deallocated here. + */ +void ipe_bdev_free_security(struct block_device *bdev) +{ + struct ipe_bdev_blob *bdev_sec = ipe_bdev(bdev); + + kfree(bdev_sec->dmverity_rh_sig); + + memset(bdev_sec, 0x0, sizeof(*bdev_sec)); +} + +/** + * ipe_bdev_setsecurity: Sets the a certain field of a block device security + * blob, based on @key. + * @bdev: The block device to source the security blob from. + * @key: The key representing the information to be stored. + * @value: The value to be stored. + * @len: The length of @value. + * + * As block-devices are a generic implementation across specific stacks, + * this allows information to be stored from various stacks. + * + * Return: + * 0 - OK + * !0 - Error + */ +int ipe_bdev_setsecurity(struct block_device *bdev, const char *key, + const void *value, size_t len) +{ + struct ipe_bdev_blob *bdev_sec = ipe_bdev(bdev); + + if (!strcmp(key, DM_VERITY_SIGNATURE_SEC_NAME)) { + bdev_sec->dmverity_rh_sig = kmemdup(value, len, GFP_KERNEL); + if (!bdev_sec->dmverity_rh_sig) + return -ENOMEM; + + bdev_sec->dmv_rh_sig_len = len; + } + + return 0; +} diff --git a/security/ipe/ipe-blobs.h b/security/ipe/ipe-blobs.h new file mode 100644 index 000000000000..7561f4cef558 --- /dev/null +++ b/security/ipe/ipe-blobs.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ +#include +#include + +#include "ipe.h" + +#ifndef IPE_BLOB_H +#define IPE_BLOB_H + +static inline struct ipe_bdev_blob *ipe_bdev(struct block_device *bdev) +{ + return bdev->security + ipe_blobs.lbs_bdev; +} + +#endif /* IPE_BLOB_H */ diff --git a/security/ipe/ipe-engine.c b/security/ipe/ipe-engine.c index 2eab680587ce..5785e51e8630 100644 --- a/security/ipe/ipe-engine.c +++ b/security/ipe/ipe-engine.c @@ -10,6 +10,7 @@ #include "ipe-engine.h" #include "ipe-audit.h" #include "ipe-pin.h" +#include "ipe-blobs.h" #include "utility.h" #include @@ -116,6 +117,9 @@ static struct ipe_engine_ctx *build_ctx(const struct file *file, local->op = op; local->hook = hook; + if (has_bdev(file)) + local->sec_bdev = ipe_bdev(bdev(file)); + return local; } diff --git a/security/ipe/ipe-engine.h b/security/ipe/ipe-engine.h index d9a95674e70d..038c39a8973e 100644 --- a/security/ipe/ipe-engine.h +++ b/security/ipe/ipe-engine.h @@ -3,20 +3,29 @@ * Copyright (C) Microsoft Corporation. All rights reserved. */ +#include "ipe.h" #include "ipe-hooks.h" #include #include #include +#include + #ifndef IPE_ENGINE_H #define IPE_ENGINE_H +struct ipe_bdev_blob { + u8 *dmverity_rh_sig; + size_t dmv_rh_sig_len; +}; + struct ipe_engine_ctx { enum ipe_op op; enum ipe_hook hook; const struct file *file; const char *audit_pathname; + const struct ipe_bdev_blob *sec_bdev; }; struct ipe_prop_cache { diff --git a/security/ipe/ipe-hooks.c b/security/ipe/ipe-hooks.c index 45efe022be04..18ab2dcd74d1 100644 --- a/security/ipe/ipe-hooks.c +++ b/security/ipe/ipe-hooks.c @@ -15,6 +15,7 @@ #include #include #include +#include /** * ipe_on_exec: LSM hook called on the exec family of system calls. diff --git a/security/ipe/ipe-hooks.h b/security/ipe/ipe-hooks.h index 5e46726f2562..b2417831cfc1 100644 --- a/security/ipe/ipe-hooks.h +++ b/security/ipe/ipe-hooks.h @@ -60,4 +60,11 @@ int ipe_on_kernel_load_data(enum kernel_load_data_id id); void ipe_sb_free_security(struct super_block *mnt_sb); +int ipe_bdev_alloc_security(struct block_device *bdev); + +void ipe_bdev_free_security(struct block_device *bdev); + +int ipe_bdev_setsecurity(struct block_device *bdev, const char *key, + const void *value, size_t len); + #endif /* IPE_HOOK_H */ diff --git a/security/ipe/ipe.c b/security/ipe/ipe.c index 4a2623962f8c..b7d112fce62f 100644 --- a/security/ipe/ipe.c +++ b/security/ipe/ipe.c @@ -24,6 +24,9 @@ static struct security_hook_list ipe_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(kernel_load_data, ipe_on_kernel_load_data), LSM_HOOK_INIT(file_mprotect, ipe_on_mprotect), LSM_HOOK_INIT(sb_free_security, ipe_sb_free_security), + LSM_HOOK_INIT(bdev_alloc_security, ipe_bdev_alloc_security), + LSM_HOOK_INIT(bdev_free_security, ipe_bdev_free_security), + LSM_HOOK_INIT(bdev_setsecurity, ipe_bdev_setsecurity), }; /** @@ -41,6 +44,10 @@ static int __init ipe_load_properties(void) if (rc != 0) return rc; + rc = ipe_init_dm_verity_signature(); + if (rc != 0) + return rc; + return rc; } @@ -73,9 +80,20 @@ static int __init ipe_init(void) return rc; } +struct lsm_blob_sizes ipe_blobs = { + .lbs_cred = 0, + .lbs_file = 0, + .lbs_inode = 0, + .lbs_ipc = 0, + .lbs_msg_msg = 0, + .lbs_task = 0, + .lbs_bdev = sizeof(struct ipe_bdev_blob), +}; + DEFINE_LSM(ipe) = { .name = "ipe", .init = ipe_init, + .blobs = &ipe_blobs, }; bool ipe_enforce = true; diff --git a/security/ipe/ipe.h b/security/ipe/ipe.h index af72bb574f73..a59cae2deec6 100644 --- a/security/ipe/ipe.h +++ b/security/ipe/ipe.h @@ -10,11 +10,13 @@ #include #include +#include #define IPE_MODE_ENFORCE "enforce" #define IPE_MODE_PERMISSIVE "permissive" extern bool ipe_enforce; extern bool ipe_success_audit; +extern struct lsm_blob_sizes ipe_blobs; #endif /* IPE_H */ diff --git a/security/ipe/properties/Kconfig b/security/ipe/properties/Kconfig index 75c6c6ff6cd8..4046f7e5eaef 100644 --- a/security/ipe/properties/Kconfig +++ b/security/ipe/properties/Kconfig @@ -13,3 +13,13 @@ config IPE_BOOT_PROP superblock. if unsure, answer N. + +config IPE_DM_VERITY_SIGNATURE + bool "Enable property for signature verified dm-verity volumes" + depends on DM_VERITY_VERIFY_ROOTHASH_SIG + help + This option enables IPE's integration with Device-Mapper Verity's + signed root-hashes. This enables the usage of the property, + "dmverity_signature" in IPE's policy. + + if unsure, answer Y. diff --git a/security/ipe/properties/Makefile b/security/ipe/properties/Makefile index e3e7fe17cf58..6b67cbe36e31 100644 --- a/security/ipe/properties/Makefile +++ b/security/ipe/properties/Makefile @@ -9,3 +9,4 @@ obj-$(CONFIG_SECURITY_IPE) += properties.o properties-$(CONFIG_IPE_BOOT_PROP) += boot-verified.o +properties-$(CONFIG_IPE_DM_VERITY_SIGNATURE) += dmverity-signature.o diff --git a/security/ipe/properties/dmverity-signature.c b/security/ipe/properties/dmverity-signature.c new file mode 100644 index 000000000000..819222f226b7 --- /dev/null +++ b/security/ipe/properties/dmverity-signature.c @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ + +#include "../ipe.h" +#include "../ipe-pin.h" +#include "../ipe-property.h" +#include "../utility.h" + +#include +#include +#include +#include +#include + +#define PROPERTY_NAME "dmverity_signature" + +static void audit(struct audit_buffer *ab, bool value) +{ + audit_log_format(ab, "%s", (value) ? "TRUE" : "FALSE"); +} + +static inline void audit_rule_value(struct audit_buffer *ab, + const void *value) +{ + audit(ab, (bool)value); +} + +static inline void audit_ctx(struct audit_buffer *ab, + const struct ipe_engine_ctx *ctx) +{ + bool b = has_bdev(ctx->file) && ctx->sec_bdev->dmverity_rh_sig; + + audit(ab, b); +} + +static bool evaluate(const struct ipe_engine_ctx *ctx, + const void *value) +{ + bool expect = (bool)value; + + if (!has_bdev(ctx->file)) + return false; + + return ((bool)ctx->sec_bdev->dmverity_rh_sig) == expect; +} + +static int parse(const char *val_str, void **value) +{ + if (strcmp("TRUE", val_str) == 0) + *value = (void *)true; + else if (strcmp("FALSE", val_str) == 0) + *value = (void *)false; + else + return -EBADMSG; + + return 0; +} + +static inline int duplicate(const void *src, void **dest) +{ + *dest = (void *)(bool)src; + + return 0; +} + +static const struct ipe_property dmv_signature = { + .property_name = PROPERTY_NAME, + .version = 1, + .eval = evaluate, + .parse = parse, + .rule_audit = audit_rule_value, + .ctx_audit = audit_ctx, + .dup = duplicate, + .free_val = NULL, +}; + +int ipe_init_dm_verity_signature(void) +{ + return ipe_register_property(&dmv_signature); +} diff --git a/security/ipe/properties/prop-entry.h b/security/ipe/properties/prop-entry.h index f598dd9608b9..85366366ff0d 100644 --- a/security/ipe/properties/prop-entry.h +++ b/security/ipe/properties/prop-entry.h @@ -17,4 +17,13 @@ static inline int __init ipe_init_bootv(void) int __init ipe_init_bootv(void); #endif /* CONFIG_IPE_BOOT_PROP */ +#ifndef CONFIG_IPE_DM_VERITY_SIGNATURE +static inline int __init ipe_init_dm_verity_signature(void) +{ + return 0; +} +#else +int __init ipe_init_dm_verity_signature(void); +#endif /* CONFIG_IPE_DM_VERITY_SIGNATURE */ + #endif /* IPE_PROP_ENTRY_H */ diff --git a/security/ipe/utility.h b/security/ipe/utility.h index a13089bb0d8f..7580f17274a3 100644 --- a/security/ipe/utility.h +++ b/security/ipe/utility.h @@ -19,4 +19,14 @@ static inline bool has_sb(const struct file *file) return has_mount(file) && file->f_path.mnt->mnt_sb; } +static inline bool has_bdev(const struct file *file) +{ + return has_sb(file) && file->f_path.mnt->mnt_sb->s_bdev; +} + +static inline struct block_device *bdev(const struct file *file) +{ + return file->f_path.mnt->mnt_sb->s_bdev; +} + #endif /* IPE_UTILITY_H */ From patchwork Fri Jul 17 23:09:38 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Deven Bowers X-Patchwork-Id: 11671415 X-Patchwork-Delegate: snitzer@redhat.com Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id B0C2A13A4 for ; Fri, 17 Jul 2020 23:21:26 +0000 (UTC) Received: from us-smtp-delivery-1.mimecast.com (us-smtp-2.mimecast.com [205.139.110.61]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 28A3B2064C for ; Fri, 17 Jul 2020 23:21:26 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 28A3B2064C Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=linux.microsoft.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=dm-devel-bounces@redhat.com Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-176-p18P4RbhPl2mN7FCYmyBlQ-1; Fri, 17 Jul 2020 19:21:23 -0400 X-MC-Unique: p18P4RbhPl2mN7FCYmyBlQ-1 Received: from smtp.corp.redhat.com (int-mx06.intmail.prod.int.phx2.redhat.com [10.5.11.16]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id E533F100A626; Fri, 17 Jul 2020 23:21:18 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.20]) by smtp.corp.redhat.com (Postfix) with ESMTPS id B677F5C1D3; Fri, 17 Jul 2020 23:21:18 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id 44E2E1809561; Fri, 17 Jul 2020 23:21:18 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.rdu2.redhat.com [10.11.54.4]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id 06HNGkv6002180 for ; Fri, 17 Jul 2020 19:16:46 -0400 Received: by smtp.corp.redhat.com (Postfix) id 3AF1F20A0535; Fri, 17 Jul 2020 23:16:46 +0000 (UTC) Delivered-To: dm-devel@redhat.com Received: from mimecast-mx02.redhat.com (mimecast03.extmail.prod.ext.rdu2.redhat.com [10.11.55.19]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 36BDB20A0534 for ; Fri, 17 Jul 2020 23:16:43 +0000 (UTC) Received: from us-smtp-1.mimecast.com (us-smtp-delivery-1.mimecast.com [205.139.110.120]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 510C6800399 for ; Fri, 17 Jul 2020 23:16:43 +0000 (UTC) Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by relay.mimecast.com with ESMTP id us-mta-16-moFNxnF9MYCR7X6Qwb8Sxg-1; Fri, 17 Jul 2020 19:16:41 -0400 X-MC-Unique: moFNxnF9MYCR7X6Qwb8Sxg-1 Received: from dede-linux-virt.corp.microsoft.com (unknown [131.107.160.54]) by linux.microsoft.com (Postfix) with ESMTPSA id ABB7720B4916; Fri, 17 Jul 2020 16:09:48 -0700 (PDT) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com ABB7720B4916 From: Deven Bowers To: agk@redhat.com, axboe@kernel.dk, snitzer@redhat.com, jmorris@namei.org, serge@hallyn.com, zohar@linux.ibm.com, viro@zeniv.linux.org.uk, paul@paul-moore.com, eparis@redhat.com, jannh@google.com, dm-devel@redhat.com, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, linux-fsdevel@vger.kernel.org, linux-block@vger.kernel.org, linux-audit@redhat.com Date: Fri, 17 Jul 2020 16:09:38 -0700 Message-Id: <20200717230941.1190744-10-deven.desai@linux.microsoft.com> In-Reply-To: <20200717230941.1190744-1-deven.desai@linux.microsoft.com> References: <20200717230941.1190744-1-deven.desai@linux.microsoft.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.78 on 10.11.54.4 X-MIME-Autoconverted: from quoted-printable to 8bit by lists01.pubmisc.prod.ext.phx2.redhat.com id 06HNGkv6002180 X-loop: dm-devel@redhat.com Cc: sashal@kernel.org, mdsakib@microsoft.com, corbet@lwn.net, linux-kernel@vger.kernel.org, pasha.tatshin@soleen.com, nramas@linux.microsoft.com, tyhicks@linux.microsoft.com, jaskarankhurana@linux.microsoft.com Subject: [dm-devel] [RFC PATCH v4 09/12] dm-verity: add bdev_setsecurity hook for root-hash X-BeenThere: dm-devel@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: device-mapper development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: dm-devel-bounces@redhat.com Errors-To: dm-devel-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.16 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Add a security hook call to set a security property of a block_device in dm-verity with the root-hash that was verified to match the merkel-tree. Signed-off-by: Deven Bowers --- drivers/md/dm-verity-target.c | 8 ++++++++ include/linux/device-mapper.h | 1 + 2 files changed, 9 insertions(+) diff --git a/drivers/md/dm-verity-target.c b/drivers/md/dm-verity-target.c index c507f3a4e237..225f67ab378d 100644 --- a/drivers/md/dm-verity-target.c +++ b/drivers/md/dm-verity-target.c @@ -16,8 +16,10 @@ #include "dm-verity.h" #include "dm-verity-fec.h" #include "dm-verity-verify-sig.h" +#include "dm-core.h" #include #include +#include #define DM_MSG_PREFIX "verity" @@ -530,6 +532,12 @@ static int verity_verify_io(struct dm_verity_io *io) return -EIO; } + r = security_bdev_setsecurity(dm_table_get_md(v->ti->table)->bdev, + DM_VERITY_ROOTHASH_SEC_NAME, + v->root_digest, v->digest_size); + if (unlikely(r < 0)) + return r; + /* * At this point, the merkel tree has finished validating. * if signature was specified, validate the signature here. diff --git a/include/linux/device-mapper.h b/include/linux/device-mapper.h index 02be0be21d38..b82e8223d52a 100644 --- a/include/linux/device-mapper.h +++ b/include/linux/device-mapper.h @@ -625,5 +625,6 @@ static inline unsigned long to_bytes(sector_t n) } #define DM_VERITY_SIGNATURE_SEC_NAME DM_NAME ".verity-sig" +#define DM_VERITY_ROOTHASH_SEC_NAME DM_NAME ".verity-rh" #endif /* _LINUX_DEVICE_MAPPER_H */ From patchwork Fri Jul 17 23:09:39 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Deven Bowers X-Patchwork-Id: 11671411 X-Patchwork-Delegate: snitzer@redhat.com Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id B985113A4 for ; Fri, 17 Jul 2020 23:21:08 +0000 (UTC) Received: from us-smtp-1.mimecast.com (us-smtp-delivery-1.mimecast.com [207.211.31.120]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 6BE2F2064C for ; Fri, 17 Jul 2020 23:21:08 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 6BE2F2064C Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=linux.microsoft.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=dm-devel-bounces@redhat.com Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-53-BKf4lU-xPfam5kL3YMtiBA-1; Fri, 17 Jul 2020 19:21:05 -0400 X-MC-Unique: BKf4lU-xPfam5kL3YMtiBA-1 Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 8210580046A; Fri, 17 Jul 2020 23:21:00 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.21]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 6340E60E3E; Fri, 17 Jul 2020 23:21:00 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id 3D9C696255; Fri, 17 Jul 2020 23:21:00 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx05.intmail.prod.int.rdu2.redhat.com [10.11.54.5]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id 06HNGnIU002260 for ; Fri, 17 Jul 2020 19:16:49 -0400 Received: by smtp.corp.redhat.com (Postfix) id 0769A115152; Fri, 17 Jul 2020 23:16:49 +0000 (UTC) Delivered-To: dm-devel@redhat.com Received: from mimecast-mx02.redhat.com (mimecast06.extmail.prod.ext.rdu2.redhat.com [10.11.55.22]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 02CAF115165 for ; Fri, 17 Jul 2020 23:16:49 +0000 (UTC) Received: from us-smtp-1.mimecast.com (us-smtp-delivery-1.mimecast.com [207.211.31.120]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id DE307185A797 for ; Fri, 17 Jul 2020 23:16:48 +0000 (UTC) Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by relay.mimecast.com with ESMTP id us-mta-303-4fAe1ySlNNu0aDsoe0cEmg-1; Fri, 17 Jul 2020 19:16:44 -0400 X-MC-Unique: 4fAe1ySlNNu0aDsoe0cEmg-1 Received: from dede-linux-virt.corp.microsoft.com (unknown [131.107.160.54]) by linux.microsoft.com (Postfix) with ESMTPSA id EC2F320B4917; Fri, 17 Jul 2020 16:09:48 -0700 (PDT) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com EC2F320B4917 From: Deven Bowers To: agk@redhat.com, axboe@kernel.dk, snitzer@redhat.com, jmorris@namei.org, serge@hallyn.com, zohar@linux.ibm.com, viro@zeniv.linux.org.uk, paul@paul-moore.com, eparis@redhat.com, jannh@google.com, dm-devel@redhat.com, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, linux-fsdevel@vger.kernel.org, linux-block@vger.kernel.org, linux-audit@redhat.com Date: Fri, 17 Jul 2020 16:09:39 -0700 Message-Id: <20200717230941.1190744-11-deven.desai@linux.microsoft.com> In-Reply-To: <20200717230941.1190744-1-deven.desai@linux.microsoft.com> References: <20200717230941.1190744-1-deven.desai@linux.microsoft.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.11.54.5 X-MIME-Autoconverted: from quoted-printable to 8bit by lists01.pubmisc.prod.ext.phx2.redhat.com id 06HNGnIU002260 X-loop: dm-devel@redhat.com Cc: sashal@kernel.org, mdsakib@microsoft.com, corbet@lwn.net, linux-kernel@vger.kernel.org, pasha.tatshin@soleen.com, nramas@linux.microsoft.com, tyhicks@linux.microsoft.com, jaskarankhurana@linux.microsoft.com Subject: [dm-devel] [RFC PATCH v4 10/12] ipe: add property for dmverity roothash X-BeenThere: dm-devel@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: device-mapper development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: dm-devel-bounces@redhat.com Errors-To: dm-devel-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.12 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=dm-devel-bounces@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Add a property to allow IPE policy to express rules around a specific root-hash of a dm-verity volume. This can be used for revocation, (when combined with the previous dm-verity property) or the authorization of a single dm-verity volume. Signed-off-by: Deven Bowers --- security/ipe/ipe-blobs.c | 9 ++ security/ipe/ipe-engine.h | 3 + security/ipe/ipe.c | 4 + security/ipe/properties/Kconfig | 13 +- security/ipe/properties/Makefile | 1 + security/ipe/properties/dmverity-roothash.c | 153 ++++++++++++++++++++ security/ipe/properties/prop-entry.h | 9 ++ 7 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 security/ipe/properties/dmverity-roothash.c diff --git a/security/ipe/ipe-blobs.c b/security/ipe/ipe-blobs.c index 03e916d13ac1..d2cdd120d5ed 100644 --- a/security/ipe/ipe-blobs.c +++ b/security/ipe/ipe-blobs.c @@ -46,6 +46,7 @@ void ipe_bdev_free_security(struct block_device *bdev) struct ipe_bdev_blob *bdev_sec = ipe_bdev(bdev); kfree(bdev_sec->dmverity_rh_sig); + kfree(bdev_sec->dmverity_rh); memset(bdev_sec, 0x0, sizeof(*bdev_sec)); } @@ -78,5 +79,13 @@ int ipe_bdev_setsecurity(struct block_device *bdev, const char *key, bdev_sec->dmv_rh_sig_len = len; } + if (!strcmp(key, DM_VERITY_ROOTHASH_SEC_NAME)) { + bdev_sec->dmverity_rh = kmemdup(value, len, GFP_KERNEL); + if (!bdev_sec->dmverity_rh) + return -ENOMEM; + + bdev_sec->rh_size = len; + } + return 0; } diff --git a/security/ipe/ipe-engine.h b/security/ipe/ipe-engine.h index 038c39a8973e..696baaa423ff 100644 --- a/security/ipe/ipe-engine.h +++ b/security/ipe/ipe-engine.h @@ -18,6 +18,9 @@ struct ipe_bdev_blob { u8 *dmverity_rh_sig; size_t dmv_rh_sig_len; + + u8 *dmverity_rh; + size_t rh_size; }; struct ipe_engine_ctx { diff --git a/security/ipe/ipe.c b/security/ipe/ipe.c index b7d112fce62f..35653fd8155d 100644 --- a/security/ipe/ipe.c +++ b/security/ipe/ipe.c @@ -48,6 +48,10 @@ static int __init ipe_load_properties(void) if (rc != 0) return rc; + rc = ipe_init_dm_verity_rh(); + if (rc != 0) + return rc; + return rc; } diff --git a/security/ipe/properties/Kconfig b/security/ipe/properties/Kconfig index 4046f7e5eaef..4f09092522d9 100644 --- a/security/ipe/properties/Kconfig +++ b/security/ipe/properties/Kconfig @@ -14,8 +14,19 @@ config IPE_BOOT_PROP if unsure, answer N. +config IPE_DM_VERITY_ROOTHASH + bool "Enable property for authorizing dm-verity volumes via root-hash" + depends on DM_VERITY + help + This option enables IPE's integration with Device-Mapper Verity. + This enables the usage of the property "dmverity_roothash" in IPE's + policy. This property allows authorization or revocation via a + a hex-string representing the roothash of a dmverity volume. + + if unsure, answer Y. + config IPE_DM_VERITY_SIGNATURE - bool "Enable property for signature verified dm-verity volumes" + bool "Enable property for verified dm-verity volumes" depends on DM_VERITY_VERIFY_ROOTHASH_SIG help This option enables IPE's integration with Device-Mapper Verity's diff --git a/security/ipe/properties/Makefile b/security/ipe/properties/Makefile index 6b67cbe36e31..d9a3807797f4 100644 --- a/security/ipe/properties/Makefile +++ b/security/ipe/properties/Makefile @@ -10,3 +10,4 @@ obj-$(CONFIG_SECURITY_IPE) += properties.o properties-$(CONFIG_IPE_BOOT_PROP) += boot-verified.o properties-$(CONFIG_IPE_DM_VERITY_SIGNATURE) += dmverity-signature.o +properties-$(CONFIG_IPE_DM_VERITY_ROOTHASH) += dmverity-roothash.o diff --git a/security/ipe/properties/dmverity-roothash.c b/security/ipe/properties/dmverity-roothash.c new file mode 100644 index 000000000000..09112e1af753 --- /dev/null +++ b/security/ipe/properties/dmverity-roothash.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) Microsoft Corporation. All rights reserved. + */ + +#include "../ipe.h" +#include "../ipe-pin.h" +#include "../ipe-property.h" +#include "../utility.h" + +#include +#include +#include +#include +#include +#include + +#define PROPERTY_NAME "dmverity_roothash" + +struct counted_array { + u8 *arr; + size_t len; +}; + +static void audit(struct audit_buffer *ab, const void *value) +{ + const struct counted_array *a = (const struct counted_array *)value; + + if (!a || a->len == 0) + audit_log_format(ab, "NULL"); + else + audit_log_n_hex(ab, a->arr, a->len); +} + +static inline void audit_rule_value(struct audit_buffer *ab, + const void *value) +{ + audit(ab, value); +} + +static inline void audit_ctx(struct audit_buffer *ab, + const struct ipe_engine_ctx *ctx) +{ + struct counted_array a; + + if (!has_bdev(ctx->file)) + return audit(ab, NULL); + + a.arr = ctx->sec_bdev->dmverity_rh; + a.len = ctx->sec_bdev->rh_size; + + return audit(ab, &a); +} + +static bool evaluate(const struct ipe_engine_ctx *ctx, + const void *value) +{ + const struct counted_array *a = (const struct counted_array *)value; + + if (!has_bdev(ctx->file)) + return false; + + if (a->len != ctx->sec_bdev->rh_size) + return false; + + return memcmp(a->arr, ctx->sec_bdev->dmverity_rh, a->len) == 0; +} + +static int parse(const char *val_str, void **value) +{ + struct counted_array *arr = NULL; + int rv = 0; + + arr = kzalloc(sizeof(*arr), GFP_KERNEL); + if (!arr) { + rv = -ENOMEM; + goto err; + } + + arr->len = strlen(val_str) / 2; + + arr->arr = kzalloc(arr->len, GFP_KERNEL); + if (!arr->arr) { + rv = -ENOMEM; + goto err; + } + + rv = hex2bin(arr->arr, val_str, arr->len); + if (rv != 0) + goto err; + + *value = arr; + return rv; +err: + if (arr) + kfree(arr->arr); + kfree(arr); + return rv; +} + +static int duplicate(const void *src, void **dest) +{ + struct counted_array *arr = NULL; + const struct counted_array *src_arr = src; + int rv = 0; + + arr = kmemdup(src_arr, sizeof(*arr), GFP_KERNEL); + if (!arr) { + rv = -ENOMEM; + goto err; + } + + arr->arr = kmemdup(src_arr->arr, src_arr->len, GFP_KERNEL); + if (!arr->arr) { + rv = -ENOMEM; + goto err; + } + + *dest = arr; + return rv; +err: + if (arr) + kfree(arr->arr); + kfree(arr); + + return rv; +} + +static void free_val(void **value) +{ + struct counted_array *a = (struct counted_array *)*value; + + if (a) + kfree(a->arr); + kfree(a); + *value = NULL; +} + +static const struct ipe_property dmv_roothash = { + .property_name = PROPERTY_NAME, + .version = 1, + .eval = evaluate, + .parse = parse, + .rule_audit = audit_rule_value, + .ctx_audit = audit_ctx, + .dup = duplicate, + .free_val = free_val, +}; + +int ipe_init_dm_verity_rh(void) +{ + return ipe_register_property(&dmv_roothash); +} diff --git a/security/ipe/properties/prop-entry.h b/security/ipe/properties/prop-entry.h index 85366366ff0d..86a360570f3b 100644 --- a/security/ipe/properties/prop-entry.h +++ b/security/ipe/properties/prop-entry.h @@ -26,4 +26,13 @@ static inline int __init ipe_init_dm_verity_signature(void) int __init ipe_init_dm_verity_signature(void); #endif /* CONFIG_IPE_DM_VERITY_SIGNATURE */ +#ifndef CONFIG_IPE_DM_VERITY_ROOTHASH +static inline int __init ipe_init_dm_verity_rh(void) +{ + return 0; +} +#else +int __init ipe_init_dm_verity_rh(void); +#endif /* CONFIG_IPE_DM_VERITY_ROOTHASH */ + #endif /* IPE_PROP_ENTRY_H */ From patchwork Fri Jul 17 23:09:40 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Deven Bowers X-Patchwork-Id: 11671419 X-Patchwork-Delegate: snitzer@redhat.com Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 713A713A4 for ; Fri, 17 Jul 2020 23:21:31 +0000 (UTC) Received: from us-smtp-1.mimecast.com (us-smtp-delivery-1.mimecast.com [207.211.31.120]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 23DA72064C for ; Fri, 17 Jul 2020 23:21:30 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 23DA72064C Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=linux.microsoft.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=dm-devel-bounces@redhat.com Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-185-z3nDAbDHNnmedqaVDVQ00w-1; Fri, 17 Jul 2020 19:21:27 -0400 X-MC-Unique: z3nDAbDHNnmedqaVDVQ00w-1 Received: from smtp.corp.redhat.com (int-mx05.intmail.prod.int.phx2.redhat.com [10.5.11.15]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id BBD07805723; Fri, 17 Jul 2020 23:21:22 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.20]) by smtp.corp.redhat.com (Postfix) with ESMTPS id A011672AD1; Fri, 17 Jul 2020 23:21:22 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id 75A7F1806B0D; Fri, 17 Jul 2020 23:21:22 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx06.intmail.prod.int.rdu2.redhat.com [10.11.54.6]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id 06HNGmr0002213 for ; Fri, 17 Jul 2020 19:16:48 -0400 Received: by smtp.corp.redhat.com (Postfix) id 8FE442156A4C; Fri, 17 Jul 2020 23:16:48 +0000 (UTC) Delivered-To: dm-devel@redhat.com Received: from mimecast-mx02.redhat.com (mimecast01.extmail.prod.ext.rdu2.redhat.com [10.11.55.17]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 8B9DF2156A4A for ; Fri, 17 Jul 2020 23:16:48 +0000 (UTC) Received: from us-smtp-1.mimecast.com (us-smtp-1.mimecast.com [207.211.31.81]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 5956F856A56 for ; Fri, 17 Jul 2020 23:16:48 +0000 (UTC) Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by relay.mimecast.com with ESMTP id us-mta-71-bsu8kyd8NBCC16ADU_2utA-1; Fri, 17 Jul 2020 19:16:44 -0400 X-MC-Unique: bsu8kyd8NBCC16ADU_2utA-1 Received: from dede-linux-virt.corp.microsoft.com (unknown [131.107.160.54]) by linux.microsoft.com (Postfix) with ESMTPSA id 3B29920B4918; Fri, 17 Jul 2020 16:09:49 -0700 (PDT) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com 3B29920B4918 From: Deven Bowers To: agk@redhat.com, axboe@kernel.dk, snitzer@redhat.com, jmorris@namei.org, serge@hallyn.com, zohar@linux.ibm.com, viro@zeniv.linux.org.uk, paul@paul-moore.com, eparis@redhat.com, jannh@google.com, dm-devel@redhat.com, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, linux-fsdevel@vger.kernel.org, linux-block@vger.kernel.org, linux-audit@redhat.com Date: Fri, 17 Jul 2020 16:09:40 -0700 Message-Id: <20200717230941.1190744-12-deven.desai@linux.microsoft.com> In-Reply-To: <20200717230941.1190744-1-deven.desai@linux.microsoft.com> References: <20200717230941.1190744-1-deven.desai@linux.microsoft.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.78 on 10.11.54.6 X-MIME-Autoconverted: from quoted-printable to 8bit by lists01.pubmisc.prod.ext.phx2.redhat.com id 06HNGmr0002213 X-loop: dm-devel@redhat.com Cc: sashal@kernel.org, mdsakib@microsoft.com, corbet@lwn.net, linux-kernel@vger.kernel.org, pasha.tatshin@soleen.com, nramas@linux.microsoft.com, tyhicks@linux.microsoft.com, jaskarankhurana@linux.microsoft.com Subject: [dm-devel] [RFC PATCH v4 11/12] documentation: add ipe documentation X-BeenThere: dm-devel@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: device-mapper development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: dm-devel-bounces@redhat.com Errors-To: dm-devel-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.15 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Add IPE's documentation to the kernel tree. Signed-off-by: Deven Bowers Acked-by: Jonathan Corbet --- Documentation/admin-guide/LSM/index.rst | 1 + Documentation/admin-guide/LSM/ipe.rst | 508 ++++++++++++++++++++++++ MAINTAINERS | 1 + 3 files changed, 510 insertions(+) create mode 100644 Documentation/admin-guide/LSM/ipe.rst diff --git a/Documentation/admin-guide/LSM/index.rst b/Documentation/admin-guide/LSM/index.rst index a6ba95fbaa9f..ce63be6d64ad 100644 --- a/Documentation/admin-guide/LSM/index.rst +++ b/Documentation/admin-guide/LSM/index.rst @@ -47,3 +47,4 @@ subdirectories. tomoyo Yama SafeSetID + ipe diff --git a/Documentation/admin-guide/LSM/ipe.rst b/Documentation/admin-guide/LSM/ipe.rst new file mode 100644 index 000000000000..2e6610c4a134 --- /dev/null +++ b/Documentation/admin-guide/LSM/ipe.rst @@ -0,0 +1,508 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Integrity Policy Enforcement (IPE) +================================== + +Overview +-------- + +IPE is a Linux Security Module which allows for a configurable policy +to enforce integrity requirements on the whole system. It attempts to +solve the issue of Code Integrity: that any code being executed (or +files being read), are identical to the version that was built by a +trusted source. + +There are multiple implementations within the Linux kernel that solve +some measure of integrity verification. For instance, device-mapper +verity, which ensures integrity for a block device, and fs-verity which +is a system that ensures integrity for a filesystem. What these +implementations lack is a measure of run-time verification that binaries +are sourced from these locations. IPE aims to address this gap. + +IPE is separated between two major components: A configurable policy, +provided by the LSM ("IPE Core"), and deterministic attributes provided +by the kernel to evaluate files against, ("IPE Properties"). + +Use Cases +--------- + +IPE is designed for use is an embedded device with a specific purpose +(e.g. network firewall device in a data center), where all software and +configuration is built and provisioned by the owner. + +Ideally, a system which leverages IPE is not intended for general +purpose computing and does not utilize any software or configuration +built by a third party. An ideal system to leverage IPE has both mutable +and immutable components, however, all binary executable code is +immutable. + +For the highest level of security, platform firmware should verify the +the kernel and optionally the root filesystem (e.g. via U-Boot verified +boot). This allows the entire system to be integrity verified. + +Known Gaps +---------- + +IPE cannot verify the integrity of anonymous executable memory, such as +the trampolines created by gcc closures and libffi, or JIT'd code. +Unfortunately, as this is dynamically generated code, there is no way +for IPE to detect that this code has not been tampered with in +transition from where it was built, to where it is running. As a result, +IPE is incapable of tackling this problem for dynamically generated +code. + +IPE cannot verify the integrity of interpreted languages' programs when +these scripts invoked via `` ``. This is because the +way interpreters execute these files, the scripts themselves are not +evaluated as executable code through one of IPE's hooks. Interpreters +can be enlightened to the usage of IPE by trying to mmap a file into +executable memory (+X), after opening the file and responding to the +error code appropriately. This also applies to included files, or high +value files, such as configuration files of critical system components. +This specific gap is planned on being addressed within IPE. + +Threat Model +------------ + +The threat type addressed by IPE is tampering of executable user-land +code beyond the initially booted kernel, and the initial verification of +kernel modules that are loaded in userland through ``modprobe`` or +``insmod``. + +Tampering violates the property of integrity. IPE's role in mitigating +this threat is to verify the integrity (and authenticity) of all +executable code and to deny their use if integrity verification fails. +IPE generates audit logs which may be utilized to detect integrity +verification failures. + +Tampering threat scenarios include modification or replacement of +executable code by a range of actors including: + +- Insiders with physical access to the hardware +- Insiders with local network access to the system +- Insiders with access to the deployment system +- Compromised internal systems under external control +- Malicious end users of the system +- Compromised end users of the system +- Remote (external) compromise of the system + +IPE does not mitigate threats arising from malicious authorized +developers, or compromised developer tools used by authorized +developers. Additionally, IPE draws hard security boundary between user +mode and kernel mode. As a result, IPE does not provide any protections +against a kernel level exploit, and a kernel-level exploit can disable +or tamper with IPE's protections. + +The root of trust for all of IPE's verifications is the +``SYSTEM_TRUSTED_KEYRING``. + +IPE Core +-------- + +IPE Policy +~~~~~~~~~~ + +IPE policy is designed to be both forward compatible and backwards +compatible. There is one required line, at the top of the policy, +indicating the policy name, and the policy version, for instance:: + + policy_name="Ex Policy" policy_version=0.0.0 + +The policy name is a unique key identifying this policy in a human +readable name. This is used to create nodes under securityfs as well as +uniquely identify policies to deploy new policies vs update existing +policies. + +The policy version indicates the current version of the policy (NOT the +policy syntax version). This is used to prevent roll-back of policy to +potentially insecure previous versions of the policy. + +The next portion of IPE policy, are rules. Rules are formed by key=value +pairs, known as properties. IPE rules require two properties: "action", +which determines what IPE does when it encounters a match against the +rule, and "op", which determines when that rule should be evaluated. +Thus, a minimal rule is:: + + op=EXECUTE action=ALLOW + +This example will allow any execution. Additional properties are used to +restrict attributes about the files being evaluated. These properties +are intended to be deterministic attributes that are resident in the +kernel. + +Order does not matter for the rule's properties - they can be listed in +any order, however it is encouraged to have the "op" property be first, +and the "action" property be last for readability. Rules are evaluated +top-to-bottom. As a result, any revocation rules, or denies should be +placed early in the file to ensure that these rules are evaluated before +as rule with "action=ALLOW" is hit. + +IPE policy is designed to be forward compatible and backwards +compatible, thus any failure to parse a rule will result in the line +being ignored, and a warning being emitted. If backwards compatibility +is not required, the kernel command line parameter and sysctl, +``ipe.strict_parse`` can be enabled, which will cause these warnings to +be fatal. + +IPE policy supports comments. The character '#' will function as a +comment, ignoring all characters to the right of '#' until the newline. + +The default behavior of IPE evaluations can also be expressed in policy, +through the ``DEFAULT`` statement. This can be done at a global level, +or a per-operation level:: + + # Global + DEFAULT action=ALLOW + + # Operation Specific + DEFAULT op=EXECUTE action=ALLOW + +A default must be set for all known operations in IPE. If you want to +preserve older policies being compatible with newer kernels that can introduce +new operations, please set a global default of 'ALLOW', and override the +defaults on a per-operation basis. + +With configurable policy-based LSMs, there's several issues with +enforcing the configurable policies at startup, around reading and +parsing the policy: + +1. The kernel *should* not read files from userland, so directly reading + the policy file is prohibited. +2. The kernel command line has a character limit, and one kernel module + should not reserve the entire character limit for its own + configuration. +3. There are various boot loaders in the kernel ecosystem, so handing + off a memory block would be costly to maintain. + +As a result, IPE has addressed this problem through a concept of a "boot +policy". A boot policy is a minimal policy, compiled into the kernel. +This policy is intended to get the system to a state where userland is +setup and ready to receive commands, at which point a more complex +policy ("user policies") can be deployed via securityfs. The boot policy +can be specified via the Kconfig, ``SECURITY_IPE_BOOT_POLICY``, which +accepts a path to a plain-text version of the IPE policy to apply. This +policy will be compiled into the kernel. If not specified, IPE will be +disabled until a policy is deployed through securityfs, and activated +through sysfs. + +Deploying Policies +^^^^^^^^^^^^^^^^^^ + +User policies as explained above, are policies that are deployed from +userland, through securityfs. These policies are signed to enforce some +level of authorization of the policies (prohibiting an attacker from +gaining root, and deploying an "allow all" policy), through the PKCS#7 +enveloped data format. These policies must be signed by a certificate +that chains to the ``SYSTEM_TRUSTED_KEYRING``. Through openssl, the +signing can be done via:: + + openssl smime -sign -in "$MY_POLICY" -signer "$MY_CERTIFICATE" \ + -inkey "$MY_PRIVATE_KEY" -binary -outform der -noattr -nodetach \ + -out "$MY_POLICY.p7s" + +Deploying the policies is done through securityfs, through the +``new_policy`` node. To deploy a policy, simply cat the file into the +securityfs node:: + + cat "$MY_POLICY.p7s" > /sys/kernel/security/ipe/new_policy + +Upon success, this will create one subdirectory under +``/sys/kernel/security/ipe/policies/``. The subdirectory will be the +``policy_name`` field of the policy deployed, so for the example above, +the directory will be ``/sys/kernel/security/ipe/policies/Ex\ Policy``. +Within this directory, there will be four files: ``raw``, ``content``, +``active``, and ``delete``. + +The ``raw`` file is rw, reading will provide the raw PKCS#7 data that +was provided to the kernel, representing the policy. Writing, will +deploy an in-place policy update - if this policy is the currently +running policy, the new updated policy will replace it immediately upon +success. + +The ``content`` file is read only. Reading will provide the PKCS#7 inner +content of the policy, which will be the plain text policy. + +The ``active`` file is used to set a policy as the currently active policy. +This file is rw, and accepts a value of ``"1"`` to set the policy as active. +Since only a single policy can be active at one time, all other policies +will be marked inactive. + +Similarly, the ``cat`` command above will result in an error upon +syntactically invalid or untrusted policies. It will also error if a +policy already exists with the same ``policy_name``. The write to the +``raw`` node will error upon syntactically invalid, untrusted policies, +or if the payload fails the version check. The write will also fail if +the ``policy_name`` in the payload does not match the existing policy. + +Deploying these policies will *not* cause IPE to start enforcing this +policy. Once deployment is successful, a policy can be marked as active, +via ``/sys/kernel/security/ipe/$policy_name/active``. IPE will enforce +whatever policy is marked as active. For our example, we can activate +the ``Ex Policy`` via:: + + echo -n 1 > "/sys/kernel/security/ipe/Ex Policy/active" + +At which point, ``Ex Policy`` will now be the enforced policy on the +system. + +.. NOTE:: + + The -n parameter is important, as it strips an additional newline. + +IPE also provides a way to delete policies. This can be done via the +``delete`` securityfs node, ``/sys/kernel/security/ipe/$policy_name/delete``. +Writing ``1`` to that file will delete that node:: + + echo -n 1 > "/sys/kernel/security/ipe/$policy_name/delete" + +There are two requirements to delete policies: + +1. The policy being deleted must not be the active policy. +2. The policy being deleted must not be the boot policy. + +.. NOTE:: + + If a MAC system is enabled, all writes to ipe's securityfs nodes require + ``CAP_MAC_ADMIN``. + +Modes +~~~~~ + +IPE supports two modes of operation: permissive (similar to SELinux's +permissive mode) and enforce. Permissive mode performs the same checks +as enforce mode, and logs policy violations, but will not enforce the +policy. This allows users to test policies before enforcing them. + +The default mode is enforce, and can be changed via the kernel command +line parameter ``ipe.enforce=(0|1)``, or the securityfs node +``/sys/kernel/security/ipe/enforce``. The ability to switch modes can +be compiled out of the LSM via setting the Kconfig +``CONFIG_SECURITY_IPE_PERMISSIVE_SWITCH`` to N. + +.. NOTE:: + + If a MAC system is enabled, all writes to ipe's securityfs nodes require + ``CAP_MAC_ADMIN``. + +Audit Events +~~~~~~~~~~~~ + +Success Auditing +^^^^^^^^^^^^^^^^ + +IPE supports success auditing. When enabled, all events that pass IPE +policy and are not blocked will emit an audit event. This is disabled by +default, and can be enabled via the kernel command line +``ipe.success_audit=(0|1)`` or the securityfs node, +``/sys/kernel/security/ipe/success_audit``. + +This is very noisy, as IPE will check every user-mode binary on the +system, but is useful for debugging policies. + +.. NOTE:: + + If a MAC system is enabled, all writes to ipe's securityfs nodes require + ``CAP_MAC_ADMIN``. + +IPE Properties +-------------- + +As explained above, IPE properties are ``key=value`` pairs expressed in +IPE policy. Two properties are built-into the policy parser: 'op' and +'action'. The other properties are determinstic attributes to express +across files. Currently those properties are: 'boot_verified', +'dmverity_signature', 'dmverity_roothash'. A description of all +properties supported by IPE are listed below: + +op +~~ + +Indicates the operation for a rule to apply to. Must be in every rule. +IPE supports the following operations: + +Version 1 +^^^^^^^^^ + +``EXECUTE`` + + Pertains to any file attempting to be executed, or loaded as an + executable. + +``FIRMWARE``: + + Pertains to firmware being loaded via the firmware_class interface. + This covers both the preallocated buffer and the firmware file + itself. + +``KMODULE``: + + Pertains to loading kernel modules via ``modprobe`` or ``insmod``. + +``KEXEC_IMAGE``: + + Pertains to kernel images loading via ``kexec``. + +``KEXEC_INITRAMFS`` + + Pertains to initrd images loading via ``kexec --initrd``. + +``POLICY``: + + Controls loading IMA policies through the + ``/sys/kernel/security/ima/policy`` securityfs entry. + +``X509_CERT``: + + Controls loading IMA certificates through the Kconfigs, + ``CONFIG_IMA_X509_PATH`` and ``CONFIG_EVM_X509_PATH``. + +``KERNEL_READ``: + + Short hand for all of the following: ``FIRMWARE``, ``KMODULE``, + ``KEXEC_IMAGE``, ``KEXEC_INITRAMFS``, ``POLICY``, and ``X509_CERT``. + +action +~~~~~~ + +Version 1 +^^^^^^^^^ + +Determines what IPE should do when a rule matches. Must be in every +rule. Can be one of: + +``ALLOW``: + + If the rule matches, explicitly allow the call to proceed without + executing any more rules. + +``DENY``: + + If the rule matches, explicitly prohibit the call from proceeding + without executing any more rules. + +boot_verified +~~~~~~~~~~~~~ + +Version 1 +^^^^^^^^^ + +This property can be utilized for authorization of the first super-block +that executes a file. This is almost always init. Typically this is used +for systems with an initramfs or other initial disk, where this is unmounted +before the system becomes available, and is not covered by any other property. +This property is controlled by the Kconfig, ``CONFIG_IPE_BOOT_PROP``. The +format of this property is:: + + boot_verified=(TRUE|FALSE) + + +.. WARNING:: + + This property will trust any disk where the first execution occurs + evaluation occurs. If you do not have a startup disk that is + unpacked and unmounted (like initramfs), then it will automatically + trust the root filesystem and potentially overauthorize the entire + disk. + +dmverity_roothash +~~~~~~~~~~~~~~~~~ + +Version 1 +^^^^^^^^^ + +This property can be utilized for authorization or revocation of +specific dm-verity volumes, identified via root hash. It has a +dependency on the DM_VERITY module. This property is controlled by the +property: ``CONFIG_IPE_DM_VERITY_ROOTHASH``. The format of this property +is:: + + dmverity_roothash=HashHexDigest + +dmverity_signature +~~~~~~~~~~~~~~~~~~ + +Version 1 +^^^^^^^^^ + +This property can be utilized for authorization of all dm-verity volumes +that have a signed roothash that chains to the system trusted keyring. +It has a dependency on the ``DM_VERITY_VERIFY_ROOTHASH_SIG`` Kconfig. +This property is controlled by the Kconfig: +``CONFIG_IPE_DM_VERITY_SIGNATURE``. The format of this property is:: + + dmverity_signature=(TRUE|FALSE) + +Policy Examples +--------------- + +Allow all +~~~~~~~~~ + +:: + + policy_name="Allow All" policy_version=0.0.0 + DEFAULT action=ALLOW + +Allow only initial superblock +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + policy_name="Allow All Initial SB" policy_version=0.0.0 + DEFAULT action=DENY + + op=EXECUTE boot_verified=TRUE action=ALLOW + +Allow any signed dm-verity volume and the initial superblock +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + policy_name="AllowSignedAndInitial" policy_version=0.0.0 + DEFAULT action=DENY + + op=EXECUTE boot_verified=TRUE action=ALLOW + op=EXECUTE dmverity_signature=TRUE action=ALLOW + +Prohibit execution from a specific dm-verity volume +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + policy_name="AllowSignedAndInitial" policy_version=0.0.0 + DEFAULT action=DENY + + op=EXECUTE dmverity_roothash=401fcec5944823ae12f62726e8184407a5fa9599783f030dec146938 action=DENY + op=EXECUTE boot_verified=TRUE action=ALLOW + op=EXECUTE dmverity_signature=TRUE action=ALLOW + +Allow only a specific dm-verity volume +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + policy_name="AllowSignedAndInitial" policy_version=0.0.0 + DEFAULT action=DENY + + op=EXECUTE dmverity_roothash=401fcec5944823ae12f62726e8184407a5fa9599783f030dec146938 action=ALLOW + +External Information +-------------------- + +Please see the github repository at: https://github.com/microsoft/ipe + +FAQ +--- + +Q: What's the difference between other LSMs which provide integrity +verification (i.e. IMA)? + +A: IPE differs from other LSMs which provide integrity checking, as it +has no dependency on the filesystem metadata itself. The attributes that +IPE checks are deterministic properties that exist solely in the kernel. +Additionally, IPE provides no additional mechanisms of verifying these +files (e.g. IMA Signatures) - all of the attributes of verifying files +are existing features within the kernel. + +Additionally, IPE is completely restricted to integrity. It offers no +measurement or attestation features, which IMA addresses. diff --git a/MAINTAINERS b/MAINTAINERS index 2876c69435d5..492de7bda6b9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8584,6 +8584,7 @@ INTEGRITY POLICY ENFORCEMENT (IPE) M: Deven Bowers L: linux-integrity@vger.kernel.org S: Supported +F: Documentation/admin-guide/LSM/ipe.rst F: scripts/ipe/ F: security/ipe/ From patchwork Fri Jul 17 23:09:41 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Deven Bowers X-Patchwork-Id: 11671403 X-Patchwork-Delegate: snitzer@redhat.com Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id E229613A4 for ; Fri, 17 Jul 2020 23:20:48 +0000 (UTC) Received: from us-smtp-delivery-1.mimecast.com (us-smtp-2.mimecast.com [205.139.110.61]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 87EF22064C for ; Fri, 17 Jul 2020 23:20:48 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 87EF22064C Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=linux.microsoft.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=dm-devel-bounces@redhat.com Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-510-9tW4jrh_Oc-0PhBQXfArKA-1; Fri, 17 Jul 2020 19:20:43 -0400 X-MC-Unique: 9tW4jrh_Oc-0PhBQXfArKA-1 Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.phx2.redhat.com [10.5.11.22]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 7761D800463; Fri, 17 Jul 2020 23:20:38 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.21]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 7B71D10013C0; Fri, 17 Jul 2020 23:20:34 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id 7CEA496255; Fri, 17 Jul 2020 23:20:31 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx05.intmail.prod.int.rdu2.redhat.com [10.11.54.5]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id 06HNGmDI002219 for ; Fri, 17 Jul 2020 19:16:48 -0400 Received: by smtp.corp.redhat.com (Postfix) id A7C5111516C; Fri, 17 Jul 2020 23:16:48 +0000 (UTC) Delivered-To: dm-devel@redhat.com Received: from mimecast-mx02.redhat.com (mimecast02.extmail.prod.ext.rdu2.redhat.com [10.11.55.18]) by smtp.corp.redhat.com (Postfix) with ESMTPS id A0ABF115165 for ; Fri, 17 Jul 2020 23:16:46 +0000 (UTC) Received: from us-smtp-1.mimecast.com (us-smtp-delivery-1.mimecast.com [207.211.31.120]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id AB4648007B1 for ; Fri, 17 Jul 2020 23:16:46 +0000 (UTC) Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by relay.mimecast.com with ESMTP id us-mta-189-nGc1STRDPwOgtfoJtFX7wg-1; Fri, 17 Jul 2020 19:16:42 -0400 X-MC-Unique: nGc1STRDPwOgtfoJtFX7wg-1 Received: from dede-linux-virt.corp.microsoft.com (unknown [131.107.160.54]) by linux.microsoft.com (Postfix) with ESMTPSA id 7A58020B491A; Fri, 17 Jul 2020 16:09:49 -0700 (PDT) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com 7A58020B491A From: Deven Bowers To: agk@redhat.com, axboe@kernel.dk, snitzer@redhat.com, jmorris@namei.org, serge@hallyn.com, zohar@linux.ibm.com, viro@zeniv.linux.org.uk, paul@paul-moore.com, eparis@redhat.com, jannh@google.com, dm-devel@redhat.com, linux-integrity@vger.kernel.org, linux-security-module@vger.kernel.org, linux-fsdevel@vger.kernel.org, linux-block@vger.kernel.org, linux-audit@redhat.com Date: Fri, 17 Jul 2020 16:09:41 -0700 Message-Id: <20200717230941.1190744-13-deven.desai@linux.microsoft.com> In-Reply-To: <20200717230941.1190744-1-deven.desai@linux.microsoft.com> References: <20200717230941.1190744-1-deven.desai@linux.microsoft.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.11.54.5 X-MIME-Autoconverted: from quoted-printable to 8bit by lists01.pubmisc.prod.ext.phx2.redhat.com id 06HNGmDI002219 X-loop: dm-devel@redhat.com Cc: sashal@kernel.org, mdsakib@microsoft.com, corbet@lwn.net, linux-kernel@vger.kernel.org, pasha.tatshin@soleen.com, nramas@linux.microsoft.com, tyhicks@linux.microsoft.com, jaskarankhurana@linux.microsoft.com Subject: [dm-devel] [RFC PATCH v4 12/12] cleanup: uapi/linux/audit.h X-BeenThere: dm-devel@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: device-mapper development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: dm-devel-bounces@redhat.com Errors-To: dm-devel-bounces@redhat.com X-Scanned-By: MIMEDefang 2.84 on 10.5.11.22 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Remove trailing whitespaces and align the integrity #defines in linux/uapi/audit.h Signed-off-by: Deven Bowers --- include/uapi/linux/audit.h | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h index 5a634cca1d42..609b4a5e8a80 100644 --- a/include/uapi/linux/audit.h +++ b/include/uapi/linux/audit.h @@ -48,7 +48,7 @@ * 2500 - 2999 future user space (maybe integrity labels and related events) * * Messages from 1000-1199 are bi-directional. 1200-1299 & 2100 - 2999 are - * exclusively user space. 1300-2099 is kernel --> user space + * exclusively user space. 1300-2099 is kernel --> user space * communication. */ #define AUDIT_GET 1000 /* Get status */ @@ -78,7 +78,7 @@ #define AUDIT_LAST_USER_MSG 1199 #define AUDIT_FIRST_USER_MSG2 2100 /* More user space messages */ #define AUDIT_LAST_USER_MSG2 2999 - + #define AUDIT_DAEMON_START 1200 /* Daemon startup record */ #define AUDIT_DAEMON_END 1201 /* Daemon normal stop record */ #define AUDIT_DAEMON_ABORT 1202 /* Daemon error stop record */ @@ -140,20 +140,20 @@ #define AUDIT_MAC_CALIPSO_ADD 1418 /* NetLabel: add CALIPSO DOI entry */ #define AUDIT_MAC_CALIPSO_DEL 1419 /* NetLabel: del CALIPSO DOI entry */ -#define AUDIT_FIRST_KERN_ANOM_MSG 1700 -#define AUDIT_LAST_KERN_ANOM_MSG 1799 -#define AUDIT_ANOM_PROMISCUOUS 1700 /* Device changed promiscuous mode */ -#define AUDIT_ANOM_ABEND 1701 /* Process ended abnormally */ -#define AUDIT_ANOM_LINK 1702 /* Suspicious use of file links */ -#define AUDIT_ANOM_CREAT 1703 /* Suspicious file creation */ -#define AUDIT_INTEGRITY_DATA 1800 /* Data integrity verification */ -#define AUDIT_INTEGRITY_METADATA 1801 /* Metadata integrity verification */ -#define AUDIT_INTEGRITY_STATUS 1802 /* Integrity enable status */ -#define AUDIT_INTEGRITY_HASH 1803 /* Integrity HASH type */ -#define AUDIT_INTEGRITY_PCR 1804 /* PCR invalidation msgs */ -#define AUDIT_INTEGRITY_RULE 1805 /* policy rule */ -#define AUDIT_INTEGRITY_EVM_XATTR 1806 /* New EVM-covered xattr */ -#define AUDIT_INTEGRITY_POLICY_RULE 1807 /* IMA policy rules */ +#define AUDIT_FIRST_KERN_ANOM_MSG 1700 +#define AUDIT_LAST_KERN_ANOM_MSG 1799 +#define AUDIT_ANOM_PROMISCUOUS 1700 /* Device changed promiscuous mode */ +#define AUDIT_ANOM_ABEND 1701 /* Process ended abnormally */ +#define AUDIT_ANOM_LINK 1702 /* Suspicious use of file links */ +#define AUDIT_ANOM_CREAT 1703 /* Suspicious file creation */ +#define AUDIT_INTEGRITY_DATA 1800 /* Data integrity verification */ +#define AUDIT_INTEGRITY_METADATA 1801 /* Metadata integrity verification */ +#define AUDIT_INTEGRITY_STATUS 1802 /* Integrity enable status */ +#define AUDIT_INTEGRITY_HASH 1803 /* Integrity HASH type */ +#define AUDIT_INTEGRITY_PCR 1804 /* PCR invalidation msgs */ +#define AUDIT_INTEGRITY_RULE 1805 /* policy rule */ +#define AUDIT_INTEGRITY_EVM_XATTR 1806 /* New EVM-covered xattr */ +#define AUDIT_INTEGRITY_POLICY_RULE 1807 /* IMA policy rules */ #define AUDIT_INTEGRITY_POLICY_LOAD 1808 /* IPE Policy Load */ #define AUDIT_INTEGRITY_POLICY_ACTIVATE 1809 /* IPE Policy Activation */ #define AUDIT_INTEGRITY_EVENT 1810 /* IPE Evaluation Event */