From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-wr1-f73.google.com (mail-wr1-f73.google.com [209.85.221.73]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A567931960A for ; Mon, 4 May 2026 08:54:33 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.73 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777884875; cv=none; b=QJexTnUYRs8oTMnEFO23x9B7e6QrSORVZUIvNTwu5PVB1hML4gBEDCdRyE+AeFiLjGthGhzkPCdu/hJo3SaO6ZjbCKjadylx+n627ElcMjwpK1FifttIG7v1jC0QBMQ2tnjcd9tVIR8fehPZfHpW0d7oMOEAgZ95enpDxZy00mE= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777884875; c=relaxed/simple; bh=OwF3L5GR1cMtE7nPtUFj655iaVQdrylgiLo3mRhFNxM=; h=Date:Mime-Version:Message-ID:Subject:From:To:Cc:Content-Type; b=pQJt3xGBW4u8LMhltVhAS0Sr1/sh5k7dhF1szYbwgPb4gCVjof3MMuWvlsq+Zaxa2txn9av3rRN9/L+NwIodKtU5zmRh8IdpTIi1wvZNcnBkU4ycYMuMR4PLupsKOHn0X8b73IzljEiu6UaINi7kWFUBnj0f6+zUCOyqpVbK65o= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--mattbobrowski.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=VkWxQ/sh; arc=none smtp.client-ip=209.85.221.73 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--mattbobrowski.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="VkWxQ/sh" Received: by mail-wr1-f73.google.com with SMTP id ffacd0b85a97d-44696b11265so4113730f8f.0 for ; Mon, 04 May 2026 01:54:33 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1777884872; x=1778489672; darn=vger.kernel.org; h=cc:to:from:subject:message-id:mime-version:date:from:to:cc:subject :date:message-id:reply-to; bh=gAuMcyKJkFzXrP1y2ma8E+PyRs3OkZhGa/JdlcjtxkE=; b=VkWxQ/shMFdho8/OlKwNABIWtZOt8MzIPhPTYhVlyXhcdylVQuXH0ltQzJ8W04W/6q YeYFGFaqwgq+jqD4kxPcBQ2Lj5JVbBQR0zuTFbdZ2+ge4xpByY3bwDi9fIEjpkghNG5l lf33838njQCD/xjI4YW4HyXAk4fcqwFspRmKQjyN600mEtZHXqMOSlhwZqDv+zJhZEzm 0WIVyZdp9L91waxSO7sUgzyaXEyYFxbKmzCDirhpCG890+3DP+JJzHMbAWtBsE3qPyoV nrVw67UHH4w9nm0OwccFBNdWylx8l5/j9fFe3qJYGs0930qZ/z1EmgtSXC0yoAPPtiXQ KgBA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1777884872; x=1778489672; h=cc:to:from:subject:message-id:mime-version:date:x-gm-message-state :from:to:cc:subject:date:message-id:reply-to; bh=gAuMcyKJkFzXrP1y2ma8E+PyRs3OkZhGa/JdlcjtxkE=; b=BTXofMVxP/t0hnvQuUJZw/U8SW0ZhDtvoq96IxrTk20z33Lain8+NE83PVSU0pYu3N rXMTYm60fUwpzHXHyztjGw8GijKB8FMB5lnxtq5iVBtrTYxszOMWiNfGkix2q3HGocax u08QJmJlfRxqcuuFiPUdI+PQrhDn2Ic/Tqti5O+liQ2RaaBZifbdSxjiobRm5saW1bTB +rjEB8D1rAk1SgmVdX1nESY2qcsmisNDMY4IqefdTgY22OYGSaBopV3fUOyJfL06ICLf Qq1E82cKTev4zAcNoBuKB3a5Xi5sd90VPzBPdVxnbvAtjEnrtmkild+e+QT5WOCa8MuR 85jw== X-Gm-Message-State: AOJu0Yx+GlWO7qR/wB/J6ispHtaPmBo4/BG4ugvUTjmPbft0bxRgBsbd QIgn2hkk9YMFr3LtAe4BcGmIQ6sRCQjNLNKNOULOhvNI1Hgwr84WogC5+jjHEb6AuH+x5CL43aI IvvgZ/DFaetiOPRMV0JfhuQv6wvwCeXZ/BTEurltmnzeyaNiDU0eay83rwS3gWFCzcZTkrUzDk9 3XBb5JHb59n7rgpXwd9a82c6L7+wNSkrUAqdSMfKKIXdBxkIcYcEEXM9UuGrTaZsvYu6f6ag== X-Received: from wrxp7.prod.google.com ([2002:a05:6000:187:b0:439:cfa2:e197]) (user=mattbobrowski job=prod-delivery.src-stubby-dispatcher) by 2002:a05:6000:18a5:b0:43d:50c:6f33 with SMTP id ffacd0b85a97d-44bb63f539cmr14449531f8f.26.1777884871591; Mon, 04 May 2026 01:54:31 -0700 (PDT) Date: Mon, 4 May 2026 08:54:27 +0000 Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 X-Mailer: git-send-email 2.54.0.545.g6539524ca2-goog Message-ID: <20260504085428.2865671-1-mattbobrowski@google.com> Subject: [PATCH v2 bpf-next 1/2] bpf: enforce VFS constraints for xattr related BPF kfuncs From: Matt Bobrowski To: bpf@vger.kernel.org Cc: Alexei Starovoitov , Daniel Borkmann , Andrii Nakryiko , Martin KaFai Lau , Eduard Zingerman , Song Liu , Yonghong Song , Jiri Olsa , Alexander Viro , Christian Brauner , Jan Kara , Kumar Kartikeya Dwivedi , Matt Bobrowski Content-Type: text/plain; charset="UTF-8" Enforce VFS constraints and semantics regarding name and value lengths within the xattr related BPF kfuncs. Specifically, reject names that are empty or longer than XATTR_NAME_MAX, and values larger than XATTR_SIZE_MAX. Also validate the supplied flags to ensure that only XATTR_CREATE and XATTR_REPLACE can be used alongside the default flag value 0. Fixes: 56467292794b ("bpf: fs/xattr: Add BPF kfuncs to set and remove xattrs") Closes: https://lore.kernel.org/bpf/20260429221005.6D1C6C19425@smtp.kernel.org/ Signed-off-by: Matt Bobrowski --- Changes in v2: - New helper bpf_xattr_validate_name() incorporated into pre-existing BPF kfunc bpf_cgroup_read_xattr() (Sashiko AI). fs/bpf_fs_kfuncs.c | 85 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 70 insertions(+), 15 deletions(-) diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c index 9d27be058494..eb9aef1e135e 100644 --- a/fs/bpf_fs_kfuncs.c +++ b/fs/bpf_fs_kfuncs.c @@ -93,6 +93,21 @@ __bpf_kfunc int bpf_path_d_path(const struct path *path, char *buf, size_t buf__ return len; } +static int bpf_xattr_validate_name(const char *name) +{ + u32 name_len; + + /* + * Impose the same restrictions on the supplied name as done so within + * the VFS by helpers like import_xattr_name(). + */ + name_len = strlen(name); + if (!name_len || name_len > XATTR_NAME_MAX) + return -ERANGE; + + return 0; +} + static bool match_security_bpf_prefix(const char *name__str) { return !strncmp(name__str, XATTR_NAME_BPF_LSM, XATTR_NAME_BPF_LSM_LEN); @@ -117,10 +132,10 @@ static int bpf_xattr_read_permission(const char *name, struct inode *inode) * @name__str: name of the xattr * @value_p: output buffer of the xattr value * - * Get xattr *name__str* of *dentry* and store the output in *value_ptr*. + * Get xattr *name__str* of *dentry* and store the output in *value_p*. * - * For security reasons, only *name__str* with prefixes "user." or - * "security.bpf." are allowed. + * For security reasons, only *name__str* values prefixed with "user." or + * "security.bpf." are permitted. * * Return: length of the xattr value on success, a negative value on error. */ @@ -133,6 +148,10 @@ __bpf_kfunc int bpf_get_dentry_xattr(struct dentry *dentry, const char *name__st void *value; int ret; + ret = bpf_xattr_validate_name(name__str); + if (ret) + return ret; + value_len = __bpf_dynptr_size(value_ptr); value = __bpf_dynptr_data_rw(value_ptr, value_len); if (!value) @@ -150,10 +169,10 @@ __bpf_kfunc int bpf_get_dentry_xattr(struct dentry *dentry, const char *name__st * @name__str: name of the xattr * @value_p: output buffer of the xattr value * - * Get xattr *name__str* of *file* and store the output in *value_ptr*. + * Get xattr *name__str* of *file* and store the output in *value_p*. * - * For security reasons, only *name__str* with prefixes "user." or - * "security.bpf." are allowed. + * For security reasons, only *name__str* values prefixed with "user." or + * "security.bpf." are permitted. * * Return: length of the xattr value on success, a negative value on error. */ @@ -187,10 +206,18 @@ static int bpf_xattr_write_permission(const char *name, struct inode *inode) * @value_p: xattr value * @flags: flags to pass into filesystem operations * - * Set xattr *name__str* of *dentry* to the value in *value_ptr*. + * Set xattr *name__str* of *dentry* to the value in *value_p*. * - * For security reasons, only *name__str* with prefix "security.bpf." - * is allowed. + * For security reasons, only *name__str* values prefixed with "security.bpf." + * are permitted. + * + * The length constraints imposed on both the xattr name and value abide those + * that are also enforced by the VFS. Additionally, the flags argument respects + * what's enforced by the VFS in the same way. By default, the flag value of 0 + * is permitted and an xattr will be created if it does not exist, or the value + * will be replaced if the xattr already exists. More course grained control + * over these exact semantics is permitted by explicitly specifying either + * XATTR_CREATE or XATTR_REPLACE. * * The caller already locked dentry->d_inode. * @@ -206,7 +233,17 @@ int bpf_set_dentry_xattr_locked(struct dentry *dentry, const char *name__str, u32 value_len; int ret; + if (flags & ~(XATTR_CREATE | XATTR_REPLACE)) + return -EINVAL; + + ret = bpf_xattr_validate_name(name__str); + if (ret) + return ret; + value_len = __bpf_dynptr_size(value_ptr); + if (value_len > XATTR_SIZE_MAX) + return -E2BIG; + value = __bpf_dynptr_data(value_ptr, value_len); if (!value) return -EINVAL; @@ -237,8 +274,8 @@ int bpf_set_dentry_xattr_locked(struct dentry *dentry, const char *name__str, * * Rmove xattr *name__str* of *dentry*. * - * For security reasons, only *name__str* with prefix "security.bpf." - * is allowed. + * For security reasons, only *name__str* values prefixed with "security.bpf." + * are permitted. * * The caller already locked dentry->d_inode. * @@ -249,6 +286,10 @@ int bpf_remove_dentry_xattr_locked(struct dentry *dentry, const char *name__str) struct inode *inode = d_inode(dentry); int ret; + ret = bpf_xattr_validate_name(name__str); + if (ret) + return ret; + ret = bpf_xattr_write_permission(name__str, inode); if (ret) return ret; @@ -274,11 +315,19 @@ __bpf_kfunc_start_defs(); * @value_p: xattr value * @flags: flags to pass into filesystem operations * - * Set xattr *name__str* of *dentry* to the value in *value_ptr*. + * Set xattr *name__str* of *dentry* to the value in *value_p*. * * For security reasons, only *name__str* with prefix "security.bpf." * is allowed. * + * The length constraints imposed on both the xattr name and value abide those + * that are also enforced by the VFS. Additionally, the flags argument respects + * what's enforced by the VFS in the same way. By default, the flag value of 0 + * is permitted and an xattr will be created if it does not exist, or the value + * will be replaced if the xattr already exists. More course grained control + * over these exact semantics is permitted by explicitly specifying either + * XATTR_CREATE or XATTR_REPLACE. + * * The caller has not locked dentry->d_inode. * * Return: 0 on success, a negative value on error. @@ -327,18 +376,24 @@ __bpf_kfunc int bpf_remove_dentry_xattr(struct dentry *dentry, const char *name_ * @name__str: name of the xattr * @value_p: output buffer of the xattr value * - * Get xattr *name__str* of *cgroup* and store the output in *value_ptr*. + * Get xattr *name__str* of *cgroup* and store the output in *value_p*. * - * For security reasons, only *name__str* with prefix "user." is allowed. + * For security reasons, only *name__str* values prefixed with "user." are + * permitted. * * Return: length of the xattr value on success, a negative value on error. */ __bpf_kfunc int bpf_cgroup_read_xattr(struct cgroup *cgroup, const char *name__str, - struct bpf_dynptr *value_p) + struct bpf_dynptr *value_p) { struct bpf_dynptr_kern *value_ptr = (struct bpf_dynptr_kern *)value_p; u32 value_len; void *value; + int ret; + + ret = bpf_xattr_validate_name(name__str); + if (ret) + return ret; /* Only allow reading "user.*" xattrs */ if (strncmp(name__str, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN)) -- 2.54.0.545.g6539524ca2-goog