From patchwork Tue Jan 12 15:09:39 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Richard Guy Briggs X-Patchwork-Id: 12013761 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.0 required=3.0 tests=BAYES_00,DKIMWL_WL_HIGH, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER,INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 414ADC43219 for ; Tue, 12 Jan 2021 15:14:15 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id F217E23131 for ; Tue, 12 Jan 2021 15:14:14 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2405116AbhALPNh (ORCPT ); Tue, 12 Jan 2021 10:13:37 -0500 Received: from us-smtp-delivery-124.mimecast.com ([216.205.24.124]:30375 "EHLO us-smtp-delivery-124.mimecast.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2405041AbhALPNd (ORCPT ); Tue, 12 Jan 2021 10:13:33 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1610464326; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:in-reply-to:in-reply-to:references:references; bh=eYAwFL1bjtKuxrU7qTxXl+J5KIWZcpoUKayCM4CfTx4=; b=R1ho4XMeBIHD8Nqf+yzS6AzVstEkShfY6rRlpqKU5viVDJvC3n9ILorPZ8y9kQGdWxZBbq zUQajotW15MTORp34iDuFzOOICWxNCBfVTHkwShjrHQz2LUqjVqCpNU/paIHqROvuEsv6F OrhdjrIGi0XVEebkLg50efTs41BW7Os= 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-585-iK-nb-EHMbicuqqU05FD5Q-1; Tue, 12 Jan 2021 10:12:04 -0500 X-MC-Unique: iK-nb-EHMbicuqqU05FD5Q-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 CEE4C100C601; Tue, 12 Jan 2021 15:12:01 +0000 (UTC) Received: from madcap2.tricolour.ca (unknown [10.10.110.8]) by smtp.corp.redhat.com (Postfix) with ESMTP id BB2325D9CD; Tue, 12 Jan 2021 15:11:55 +0000 (UTC) From: Richard Guy Briggs To: Linux Containers List , Linux API , Linux-Audit Mailing List , Linux FSdevel , LKML , Linux NetDev Upstream Mailing List , Netfilter Devel List Cc: Neil Horman , David Howells , "Eric W. Biederman" , Simo Sorce , Eric Paris , mpatel@redhat.com, Serge Hallyn , Kees Cook , Jens Axboe , Christian Brauner , Richard Guy Briggs Subject: [PATCH ghak90 v11 11/11] audit: add capcontid to set contid outside init_user_ns Date: Tue, 12 Jan 2021 10:09:39 -0500 Message-Id: In-Reply-To: References: X-Scanned-By: MIMEDefang 2.79 on 10.5.11.14 Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org Provide a mechanism similar to CAP_AUDIT_CONTROL to explicitly give a process in a non-init user namespace the capability to set audit container identifiers of individual children. Provide the /proc/$PID/audit_capcontid interface to capcontid. Valid values are: 1==enabled, 0==disabled Writing a "1" to this special file for the target process $PID will enable the target process to set audit container identifiers of its descendants. A process must already have CAP_AUDIT_CONTROL in the initial user namespace or have had audit_capcontid enabled by a previous use of this feature by its parent on this process in order to be able to enable it for another process. The target process must be a descendant of the calling process. Report this action in new message type AUDIT_SET_CAPCONTID 1022 with fields opid= capcontid= old-capcontid= Add an entry to Documentation/ABI. Signed-off-by: Richard Guy Briggs --- .../ABI/testing/procfs-audit_containerid | 16 +++++ fs/proc/base.c | 54 +++++++++++++++ include/linux/audit.h | 4 +- include/uapi/linux/audit.h | 1 + kernel/audit.c | 65 ++++++++++++++++++- 5 files changed, 137 insertions(+), 3 deletions(-) diff --git a/Documentation/ABI/testing/procfs-audit_containerid b/Documentation/ABI/testing/procfs-audit_containerid index 30ea64790473..c697d7da0ad1 100644 --- a/Documentation/ABI/testing/procfs-audit_containerid +++ b/Documentation/ABI/testing/procfs-audit_containerid @@ -11,3 +11,19 @@ Description: or have its own /proc/$pid/capcontainerid set to write or read. + +What: Capability to set or get the Audit Container Identifier +Date: 2020-?? +KernelVersion: 5.10? +Contact: linux-audit@redhat.com +Format: u32 +Users: auditd, libaudit, audit-testsuite, podman(?), container orchestrators +Description: + The /proc/$pid/audit_capcontainerid pseudofile is + written to set and is read to get the capability of + process $pid to write or to read the /proc/$pid/containerid + audit container identifier of any of its descendants. + "1" allows and "0" denies that capability. This + property is an extension to CAP_AUDIT_CONTROL outside of + the initial user namespace. + diff --git a/fs/proc/base.c b/fs/proc/base.c index bf447e7932d2..7495eec1c73b 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -1383,6 +1383,58 @@ static const struct file_operations proc_contid_operations = { .write = proc_contid_write, .llseek = generic_file_llseek, }; + +static ssize_t proc_capcontid_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct inode *inode = file_inode(file); + struct task_struct *task = get_proc_task(inode); + ssize_t length; + char tmpbuf[TMPBUFLEN]; + + if (!task) + return -ESRCH; + length = audit_get_capcontid_proc(tmpbuf, TMPBUFLEN, task); + put_task_struct(task); + if (length < 0) + return length; + return simple_read_from_buffer(buf, count, ppos, tmpbuf, length); +} + +static ssize_t proc_capcontid_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct inode *inode = file_inode(file); + u32 capcontid; + int rv; + struct task_struct *task = get_proc_task(inode); + + if (!task) + return -ESRCH; + if (*ppos != 0) { + /* No partial writes. */ + put_task_struct(task); + return -EINVAL; + } + + rv = kstrtou32_from_user(buf, count, 10, &capcontid); + if (rv < 0) { + put_task_struct(task); + return rv; + } + + rv = audit_set_capcontid(task, capcontid); + put_task_struct(task); + if (rv < 0) + return rv; + return count; +} + +static const struct file_operations proc_capcontid_operations = { + .read = proc_capcontid_read, + .write = proc_capcontid_write, + .llseek = generic_file_llseek, +}; #endif #ifdef CONFIG_FAULT_INJECTION @@ -3286,6 +3338,7 @@ static const struct pid_entry tgid_base_stuff[] = { REG("loginuid", S_IWUSR|S_IRUGO, proc_loginuid_operations), REG("sessionid", S_IRUGO, proc_sessionid_operations), REG("audit_containerid", S_IWUSR|S_IRUSR, proc_contid_operations), + REG("audit_capcontainerid", S_IWUSR|S_IRUSR, proc_capcontid_operations), #endif #ifdef CONFIG_FAULT_INJECTION REG("make-it-fail", S_IRUGO|S_IWUSR, proc_fault_inject_operations), @@ -3629,6 +3682,7 @@ static const struct pid_entry tid_base_stuff[] = { REG("loginuid", S_IWUSR|S_IRUGO, proc_loginuid_operations), REG("sessionid", S_IRUGO, proc_sessionid_operations), REG("audit_containerid", S_IWUSR|S_IRUSR, proc_contid_operations), + REG("audit_capcontainerid", S_IWUSR|S_IRUSR, proc_capcontid_operations), #endif #ifdef CONFIG_FAULT_INJECTION REG("make-it-fail", S_IRUGO|S_IWUSR, proc_fault_inject_operations), diff --git a/include/linux/audit.h b/include/linux/audit.h index 014f73296fec..3f5444393618 100644 --- a/include/linux/audit.h +++ b/include/linux/audit.h @@ -214,8 +214,10 @@ extern void audit_set_sessionid_iouring(unsigned int sessionid); extern int audit_get_contid_proc(char *tmpbuf, int TMPBUFLEN, struct task_struct *task); - +extern int audit_get_capcontid_proc(char *tmpbuf, int TMPBUFLEN, + struct task_struct *task); extern int audit_set_contid(struct task_struct *tsk, u64 contid); +extern int audit_set_capcontid(struct task_struct *tsk, u32 enable); extern void audit_copy_namespaces(struct net *net, struct task_struct *tsk); extern void audit_switch_task_namespaces(struct nsproxy *ns, diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h index 66350e572e41..b7c7ab2e6a1d 100644 --- a/include/uapi/linux/audit.h +++ b/include/uapi/linux/audit.h @@ -73,6 +73,7 @@ #define AUDIT_GET_FEATURE 1019 /* Get which features are enabled */ #define AUDIT_CONTAINER_OP 1020 /* Define the container id and info */ #define AUDIT_SIGNAL_INFO2 1021 /* Get info auditd signal sender */ +#define AUDIT_SET_CAPCONTID 1022 /* Set cap_contid of a task */ #define AUDIT_FIRST_USER_MSG 1100 /* Userspace messages mostly uninteresting to kernel */ #define AUDIT_USER_AVC 1107 /* We filter this differently */ diff --git a/kernel/audit.c b/kernel/audit.c index d2e9d803e5fd..7e825305b3c2 100644 --- a/kernel/audit.c +++ b/kernel/audit.c @@ -238,6 +238,7 @@ struct audit_task_info { kuid_t loginuid; unsigned int sessionid; struct audit_contobj *cont; + u32 capcontid; #ifdef CONFIG_AUDITSYSCALL struct audit_context *ctx; #endif @@ -308,6 +309,15 @@ static inline u64 audit_get_contid(struct task_struct *tsk) return info->cont->id; } +static inline u32 audit_get_capcontid(struct task_struct *tsk) +{ + struct audit_task_info *info = tsk->audit; + + if (!info) + return 0; + return info->capcontid; +} + inline struct audit_context *_audit_context(struct task_struct *tsk) { struct audit_task_info *info = tsk->audit; @@ -420,6 +430,7 @@ static void audit_alloc_task(struct task_struct *tsk) rcu_read_lock(); info->cont = _audit_contobj_get_bytask(current); rcu_read_unlock(); + info->capcontid = 0; tsk->audit = info; } @@ -2988,6 +2999,56 @@ static bool audit_contid_isnesting(struct task_struct *tsk) return !isowner && ownerisparent; } +int audit_set_capcontid(struct task_struct *tsk, u32 enable) +{ + u32 oldcapcontid; + int rc = 0; + struct audit_buffer *ab; + struct audit_task_info *info = tsk->audit; + + if (!info) + return -ENOPROTOOPT; + oldcapcontid = audit_get_capcontid(tsk); + /* if task is not descendant, block */ + if (tsk == current || !task_is_descendant(current, tsk)) + rc = -EXDEV; + else if (current_user_ns() == &init_user_ns) { + if (!capable(CAP_AUDIT_CONTROL) && + !audit_get_capcontid(current)) + rc = -EPERM; + } + if (!rc) + info->capcontid = enable; + + if (!audit_enabled) + return rc; + + ab = audit_log_start(audit_context(), GFP_KERNEL, AUDIT_SET_CAPCONTID); + if (!ab) + return rc; + + audit_log_format(ab, + "opid=%d capcontid=%u old-capcontid=%u", + task_tgid_nr(tsk), enable, oldcapcontid); + audit_log_end(ab); + return rc; +} + +int audit_get_capcontid_proc(char *tmpbuf, int TMPBUFLEN, + struct task_struct *tsk) +{ + int length; + + /* if we don't have caps, reject */ + if (!capable(CAP_AUDIT_CONTROL) && !audit_get_capcontid(current)) { + length = -EPERM; + goto out; + } + length = scnprintf(tmpbuf, TMPBUFLEN, "%u", audit_get_capcontid(tsk)); +out: + return length; +} + /* * audit_set_contid - set current task's audit contid * @tsk: target task @@ -3021,7 +3082,7 @@ int audit_set_contid(struct task_struct *tsk, u64 contid) if (contid == AUDIT_CID_UNSET) { /* Don't allow the contid to be unset */ rc = -EINVAL; - } else if (!capable(CAP_AUDIT_CONTROL)) { + } else if (!capable(CAP_AUDIT_CONTROL) && !audit_get_capcontid(current)) { /* if we don't have caps, reject */ rc = -EPERM; } else if (!list_empty(&tsk->children) || @@ -3111,7 +3172,7 @@ int audit_get_contid_proc(char *tmpbuf, int TMPBUFLEN, int length; /* if we don't have caps, reject */ - if (!capable(CAP_AUDIT_CONTROL)) { + if (!capable(CAP_AUDIT_CONTROL) && !audit_get_capcontid(current)) { length = -EPERM; goto out; }