From nobody Thu Mar 19 02:04:58 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4253B202F70; Mon, 16 Feb 2026 13:32:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771248742; cv=none; b=omnHEjpUSPDjCpryoZRXCRhKLSQ+WbaAG2tHwQUzlMqksWZ4N4L05bX0pvntFB8tzLWf4rdX9KnomqmmL2BZRvQ5RUkRJgM2kc3QI7rt+dtNDgIWmcWYG6e0myfaKctfagl/ABXMwf+Jy1uu5GNAVvLHja8uhqW6ekXVPOf94PM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771248742; c=relaxed/simple; bh=GQODvPKfpe1TuAASNlfZOpKx+opiPpT2Vm9Zk2O6rrw=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=KJkoR4vwagPSl62cI9KAFSSW3lDeG6VQQSNjrZMS+ZEPE5q2yM2Xqfiots6CZ0Fft2r10QQzFqsxRZ3ZAMy61TQTRKwJ99i5noghEoCQ/pL1gXHLPintMtS+3DUBvWXQ+HKrUBGiPApv0bCn/0aTwCBxajNCF9fGfQ6kKtvJigA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=LpeBzW5m; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="LpeBzW5m" Received: by smtp.kernel.org (Postfix) with ESMTPSA id DC1D2C19423; Mon, 16 Feb 2026 13:32:18 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1771248741; bh=GQODvPKfpe1TuAASNlfZOpKx+opiPpT2Vm9Zk2O6rrw=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=LpeBzW5mKUxVFtLej99Vdf/DLsg/Lz5wVffdIB06ucKJPM75RkE2zw6eu2gjLAXY2 el/H2qkyx1qaloadnIfPom8wn/ga6OBtW96Kv80BBigoWZAesiZnp3CaXK5yO1AZdU OPX9Xd/XtjLC1QmeBSXIZmPE9mhstsztwVmmhHQGEoFGOQ0V9kguWN4aILDUDeOb88 EkJJN7eIrl4ryh405XEph+QaqlOLsNFbrzrvZN28wgbc2mL43EzhIvvPbiC75ijZbf h6at3a+Jt1uSrnUQFUHnP/J3xlPzVLSj0LZrGXC4SfyZ1+k0pOlk2cvdjPTR0Ms4dg X9H6BjBtmKm2w== From: Christian Brauner Date: Mon, 16 Feb 2026 14:31:57 +0100 Subject: [PATCH 01/14] xattr: add rcu_head and rhash_head to struct simple_xattr Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260216-work-xattr-socket-v1-1-c2efa4f74cb7@kernel.org> References: <20260216-work-xattr-socket-v1-0-c2efa4f74cb7@kernel.org> In-Reply-To: <20260216-work-xattr-socket-v1-0-c2efa4f74cb7@kernel.org> To: linux-fsdevel@vger.kernel.org Cc: Jeff Layton , Josef Bacik , Alexander Viro , Jan Kara , linux-kernel@vger.kernel.org, Hugh Dickins , linux-mm@kvack.org, Greg Kroah-Hartman , Tejun Heo , Eric Dumazet , Jakub Kicinski , Jann Horn , netdev@vger.kernel.org, Christian Brauner X-Mailer: b4 0.15-dev-47773 X-Developer-Signature: v=1; a=openpgp-sha256; l=2838; i=brauner@kernel.org; h=from:subject:message-id; bh=GQODvPKfpe1TuAASNlfZOpKx+opiPpT2Vm9Zk2O6rrw=; b=owGbwMvMwCU28Zj0gdSKO4sYT6slMWROlomTaIl83Gt+OeTiNWF1CbMFL08cD3PetfQlJ0PZx WbTawUuHaUsDGJcDLJiiiwO7Sbhcst5KjYbZWrAzGFlAhnCwMUpABMxPMrwz1Ci5rhPAFOf3LKl 78uOf5myJuvMsYUScicmG/244j11URIjw3++OjWV4xdd132cI+jgbPpzp/iMCku2ZWmmKV6996u YWQA= X-Developer-Key: i=brauner@kernel.org; a=openpgp; fpr=4880B8C9BD0E5106FC070F4F7B3C391EFEA93624 In preparation for converting simple_xattrs from rbtree to rhashtable, add rhash_head and rcu_head members to struct simple_xattr. The rhashtable implementation will use rhash_head for hash table linkage and RCU-based lockless reads, requiring that replaced or removed xattr entries be freed via call_rcu() rather than immediately. Add simple_xattr_free_rcu() which schedules RCU-deferred freeing of an xattr entry. This will be used by callers of simple_xattr_set() once they switch to the rhashtable-based xattr store. No functional changes. Signed-off-by: Christian Brauner Acked-by: "Darrick J. Wong" Reviewed-by: Jan Kara Reviewed-by: Jeff Layton --- fs/xattr.c | 23 +++++++++++++++++++++++ include/linux/xattr.h | 4 ++++ 2 files changed, 27 insertions(+) diff --git a/fs/xattr.c b/fs/xattr.c index 3e49e612e1ba..9cbb1917bcb2 100644 --- a/fs/xattr.c +++ b/fs/xattr.c @@ -1197,6 +1197,29 @@ void simple_xattr_free(struct simple_xattr *xattr) kvfree(xattr); } =20 +static void simple_xattr_rcu_free(struct rcu_head *head) +{ + struct simple_xattr *xattr; + + xattr =3D container_of(head, struct simple_xattr, rcu); + simple_xattr_free(xattr); +} + +/** + * simple_xattr_free_rcu - free an xattr object after an RCU grace period + * @xattr: the xattr object + * + * Schedule RCU-deferred freeing of an xattr entry. This is used by + * rhashtable-based callers of simple_xattr_set() that replace or remove + * an existing entry while concurrent RCU readers may still be accessing + * it. + */ +void simple_xattr_free_rcu(struct simple_xattr *xattr) +{ + if (xattr) + call_rcu(&xattr->rcu, simple_xattr_rcu_free); +} + /** * simple_xattr_alloc - allocate new xattr object * @value: value of the xattr object diff --git a/include/linux/xattr.h b/include/linux/xattr.h index 64e9afe7d647..1328f2bfd2ce 100644 --- a/include/linux/xattr.h +++ b/include/linux/xattr.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include =20 @@ -112,6 +113,8 @@ struct simple_xattrs { =20 struct simple_xattr { struct rb_node rb_node; + struct rhash_head hash_node; + struct rcu_head rcu; char *name; size_t size; char value[]; @@ -122,6 +125,7 @@ void simple_xattrs_free(struct simple_xattrs *xattrs, s= ize_t *freed_space); size_t simple_xattr_space(const char *name, size_t size); struct simple_xattr *simple_xattr_alloc(const void *value, size_t size); void simple_xattr_free(struct simple_xattr *xattr); +void simple_xattr_free_rcu(struct simple_xattr *xattr); int simple_xattr_get(struct simple_xattrs *xattrs, const char *name, void *buffer, size_t size); struct simple_xattr *simple_xattr_set(struct simple_xattrs *xattrs, --=20 2.47.3 From nobody Thu Mar 19 02:04:58 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E9B7D28C871; Mon, 16 Feb 2026 13:32:25 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771248746; cv=none; b=GYKqv6F0yP7aVWsucZHg0umSDIEpb/dZ28dQGBQzPeIQTXinItImuVroxyVL5/EhTxFVLVnd+lLXJ5Q3r1GJJk9l57W+ip1l6W1D8EAZ0dR/96Knd94q6rk2nNyUcQlGOnvt+Aeed4T76EWM2jA3PXKXgcDVXDFDtIWCDeGKIP0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771248746; c=relaxed/simple; bh=lRkBebN2DyEFUov+B67VPOUqf4avYVkt75F229kVmKA=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=lycIU9tpsEauM4YboOCszUbuF7fihGvg9rJNHRqMfSw+NLZPxCNu22fbVDpLPOHhUVRHaXsoFTN7lPIbJ1FwVEEV1RQzgyzYTrybUGG7Z+aCITqBdrtA76lbt6nq2i4+C0GR4zfwPS61yVXGU/iEXre5fI8tnLTEoafNLaXWpbk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=Bz3aunfe; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="Bz3aunfe" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 6441EC116C6; Mon, 16 Feb 2026 13:32:22 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1771248745; bh=lRkBebN2DyEFUov+B67VPOUqf4avYVkt75F229kVmKA=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=Bz3aunfeQSsNjNdeZv+l+2mfzrW7SqsFW6w2aPYJHxtWTtGbJ7ueorBgxhU1gVxhe g7waQLA/L1a0UJ+Z7yVV4usP1h3gmW7kll5xXeIGbTBVKW+FE+9Mg+vOjn2UCQfE8r miev+Oy6HkW96xenVd2yAn1a7eOh6UJgf/0kKrLye9uWmIXVz8b7tAszmYhrAXmwl3 HIVv/u3LSzO72V2J4fSgA4x30W1u5dkG8+EZeY2yGHlI0BEdoAmw3Zne6vmOiSSnNL k+vqxcKV+u+usSUMYKWvVXFGtazP4eypDw5dLaWQIEpiWv7fMIRmtkbvN81ZU9kfKr JEx5TjayzWvuQ== From: Christian Brauner Date: Mon, 16 Feb 2026 14:31:58 +0100 Subject: [PATCH 02/14] xattr: add rhashtable-based simple_xattr infrastructure Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260216-work-xattr-socket-v1-2-c2efa4f74cb7@kernel.org> References: <20260216-work-xattr-socket-v1-0-c2efa4f74cb7@kernel.org> In-Reply-To: <20260216-work-xattr-socket-v1-0-c2efa4f74cb7@kernel.org> To: linux-fsdevel@vger.kernel.org Cc: Jeff Layton , Josef Bacik , Alexander Viro , Jan Kara , linux-kernel@vger.kernel.org, Hugh Dickins , linux-mm@kvack.org, Greg Kroah-Hartman , Tejun Heo , Eric Dumazet , Jakub Kicinski , Jann Horn , netdev@vger.kernel.org, Christian Brauner X-Mailer: b4 0.15-dev-47773 X-Developer-Signature: v=1; a=openpgp-sha256; l=19222; i=brauner@kernel.org; h=from:subject:message-id; bh=lRkBebN2DyEFUov+B67VPOUqf4avYVkt75F229kVmKA=; b=owGbwMvMwCU28Zj0gdSKO4sYT6slMWROlonTUGbJPHl0a927JS9eBklpqNh/vWX2ePZKztSD1 Suv2xgUdJSyMIhxMciKKbI4tJuEyy3nqdhslKkBM4eVCWQIAxenAEwkbB7D/0TRXstZP6e4f2+8 fqVuGfe6i/nm0dPiD/v8UshJe8sm+Inhv++u5+fNcmRdZl5a8PpBgq+44aeM5pZ3U3UZP/zXnyN dzQ0A X-Developer-Key: i=brauner@kernel.org; a=openpgp; fpr=4880B8C9BD0E5106FC070F4F7B3C391EFEA93624 Add rhashtable support to the simple_xattr subsystem while keeping the existing rbtree code fully functional. This allows consumers to be migrated one at a time without breaking any intermediate build. struct simple_xattrs gains a dispatch flag and a union holding either the rbtree (rb_root + rwlock) or rhashtable state: struct simple_xattrs { bool use_rhashtable; union { struct { struct rb_root rb_root; rwlock_t lock; }; struct rhashtable ht; }; }; simple_xattrs_init() continues to set up the rbtree path for existing embedded-struct callers. Add simple_xattrs_alloc() which dynamically allocates a simple_xattrs and initializes the rhashtable path. This is the entry point for consumers switching to pointer-based lazy allocation. The five core functions (get, set, list, add, free) dispatch based on the use_rhashtable flag. Existing callers continue to use the rbtree path unchanged. As each consumer is converted it will switch to simple_xattrs_alloc() and the rhashtable path. Once all consumers are converted a follow-up patch will remove the rbtree code. Signed-off-by: Christian Brauner Acked-by: "Darrick J. Wong" Reviewed-by: Jan Kara Reviewed-by: Jeff Layton --- fs/xattr.c | 439 ++++++++++++++++++++++++++++++++++++++--------= ---- include/linux/xattr.h | 25 ++- mm/shmem.c | 2 +- 3 files changed, 357 insertions(+), 109 deletions(-) diff --git a/fs/xattr.c b/fs/xattr.c index 9cbb1917bcb2..1d98ea459b7b 100644 --- a/fs/xattr.c +++ b/fs/xattr.c @@ -22,6 +22,7 @@ #include #include #include +#include =20 #include =20 @@ -1228,22 +1229,25 @@ void simple_xattr_free_rcu(struct simple_xattr *xat= tr) * Allocate a new xattr object and initialize respective members. The call= er is * responsible for handling the name of the xattr. * - * Return: On success a new xattr object is returned. On failure NULL is - * returned. + * Return: New xattr object on success, NULL if @value is NULL, ERR_PTR on + * failure. */ struct simple_xattr *simple_xattr_alloc(const void *value, size_t size) { struct simple_xattr *new_xattr; size_t len; =20 + if (!value) + return NULL; + /* wrap around? */ len =3D sizeof(*new_xattr) + size; if (len < sizeof(*new_xattr)) - return NULL; + return ERR_PTR(-ENOMEM); =20 new_xattr =3D kvmalloc(len, GFP_KERNEL_ACCOUNT); if (!new_xattr) - return NULL; + return ERR_PTR(-ENOMEM); =20 new_xattr->size =3D size; memcpy(new_xattr->value, value, size); @@ -1287,6 +1291,33 @@ static int rbtree_simple_xattr_node_cmp(struct rb_no= de *new_node, return rbtree_simple_xattr_cmp(xattr->name, node); } =20 +static u32 simple_xattr_hashfn(const void *data, u32 len, u32 seed) +{ + const char *name =3D data; + return jhash(name, strlen(name), seed); +} + +static u32 simple_xattr_obj_hashfn(const void *obj, u32 len, u32 seed) +{ + const struct simple_xattr *xattr =3D obj; + return jhash(xattr->name, strlen(xattr->name), seed); +} + +static int simple_xattr_obj_cmpfn(struct rhashtable_compare_arg *arg, + const void *obj) +{ + const struct simple_xattr *xattr =3D obj; + return strcmp(xattr->name, arg->key); +} + +static const struct rhashtable_params simple_xattr_params =3D { + .head_offset =3D offsetof(struct simple_xattr, hash_node), + .hashfn =3D simple_xattr_hashfn, + .obj_hashfn =3D simple_xattr_obj_hashfn, + .obj_cmpfn =3D simple_xattr_obj_cmpfn, + .automatic_shrinking =3D true, +}; + /** * simple_xattr_get - get an xattr object * @xattrs: the header of the xattr object @@ -1306,22 +1337,41 @@ int simple_xattr_get(struct simple_xattrs *xattrs, = const char *name, void *buffer, size_t size) { struct simple_xattr *xattr =3D NULL; - struct rb_node *rbp; int ret =3D -ENODATA; =20 - read_lock(&xattrs->lock); - rbp =3D rb_find(name, &xattrs->rb_root, rbtree_simple_xattr_cmp); - if (rbp) { - xattr =3D rb_entry(rbp, struct simple_xattr, rb_node); - ret =3D xattr->size; - if (buffer) { - if (size < xattr->size) - ret =3D -ERANGE; - else - memcpy(buffer, xattr->value, xattr->size); + if (xattrs->use_rhashtable) { + guard(rcu)(); + xattr =3D rhashtable_lookup(&xattrs->ht, name, + simple_xattr_params); + if (xattr) { + ret =3D xattr->size; + if (buffer) { + if (size < xattr->size) + ret =3D -ERANGE; + else + memcpy(buffer, xattr->value, + xattr->size); + } + } + } else { + struct rb_node *rbp; + + read_lock(&xattrs->lock); + rbp =3D rb_find(name, &xattrs->rb_root, + rbtree_simple_xattr_cmp); + if (rbp) { + xattr =3D rb_entry(rbp, struct simple_xattr, rb_node); + ret =3D xattr->size; + if (buffer) { + if (size < xattr->size) + ret =3D -ERANGE; + else + memcpy(buffer, xattr->value, + xattr->size); + } } + read_unlock(&xattrs->lock); } - read_unlock(&xattrs->lock); return ret; } =20 @@ -1355,78 +1405,134 @@ struct simple_xattr *simple_xattr_set(struct simpl= e_xattrs *xattrs, const char *name, const void *value, size_t size, int flags) { - struct simple_xattr *old_xattr =3D NULL, *new_xattr =3D NULL; - struct rb_node *parent =3D NULL, **rbp; - int err =3D 0, ret; + struct simple_xattr *old_xattr =3D NULL; + int err =3D 0; =20 - /* value =3D=3D NULL means remove */ - if (value) { - new_xattr =3D simple_xattr_alloc(value, size); - if (!new_xattr) - return ERR_PTR(-ENOMEM); + CLASS(simple_xattr, new_xattr)(value, size); + if (IS_ERR(new_xattr)) + return new_xattr; =20 + if (new_xattr) { new_xattr->name =3D kstrdup(name, GFP_KERNEL_ACCOUNT); - if (!new_xattr->name) { - simple_xattr_free(new_xattr); + if (!new_xattr->name) return ERR_PTR(-ENOMEM); - } } =20 - write_lock(&xattrs->lock); - rbp =3D &xattrs->rb_root.rb_node; - while (*rbp) { - parent =3D *rbp; - ret =3D rbtree_simple_xattr_cmp(name, *rbp); - if (ret < 0) - rbp =3D &(*rbp)->rb_left; - else if (ret > 0) - rbp =3D &(*rbp)->rb_right; - else - old_xattr =3D rb_entry(*rbp, struct simple_xattr, rb_node); - if (old_xattr) - break; - } + if (xattrs->use_rhashtable) { + /* + * Lookup is safe without RCU here since writes are + * serialized by the caller. + */ + old_xattr =3D rhashtable_lookup_fast(&xattrs->ht, name, + simple_xattr_params); + + if (old_xattr) { + /* Fail if XATTR_CREATE is requested and the xattr exists. */ + if (flags & XATTR_CREATE) + return ERR_PTR(-EEXIST); + + if (new_xattr) { + err =3D rhashtable_replace_fast(&xattrs->ht, + &old_xattr->hash_node, + &new_xattr->hash_node, + simple_xattr_params); + if (err) + return ERR_PTR(err); + } else { + err =3D rhashtable_remove_fast(&xattrs->ht, + &old_xattr->hash_node, + simple_xattr_params); + if (err) + return ERR_PTR(err); + } + } else { + /* Fail if XATTR_REPLACE is requested but no xattr is found. */ + if (flags & XATTR_REPLACE) + return ERR_PTR(-ENODATA); + + /* + * If XATTR_CREATE or no flags are specified together + * with a new value simply insert it. + */ + if (new_xattr) { + err =3D rhashtable_insert_fast(&xattrs->ht, + &new_xattr->hash_node, + simple_xattr_params); + if (err) + return ERR_PTR(err); + } =20 - if (old_xattr) { - /* Fail if XATTR_CREATE is requested and the xattr exists. */ - if (flags & XATTR_CREATE) { - err =3D -EEXIST; - goto out_unlock; + /* + * If XATTR_CREATE or no flags are specified and + * neither an old or new xattr exist then we don't + * need to do anything. + */ } - - if (new_xattr) - rb_replace_node(&old_xattr->rb_node, - &new_xattr->rb_node, &xattrs->rb_root); - else - rb_erase(&old_xattr->rb_node, &xattrs->rb_root); } else { - /* Fail if XATTR_REPLACE is requested but no xattr is found. */ - if (flags & XATTR_REPLACE) { - err =3D -ENODATA; - goto out_unlock; - } + struct rb_node *parent =3D NULL, **rbp; + int ret; =20 - /* - * If XATTR_CREATE or no flags are specified together with a - * new value simply insert it. - */ - if (new_xattr) { - rb_link_node(&new_xattr->rb_node, parent, rbp); - rb_insert_color(&new_xattr->rb_node, &xattrs->rb_root); + write_lock(&xattrs->lock); + rbp =3D &xattrs->rb_root.rb_node; + while (*rbp) { + parent =3D *rbp; + ret =3D rbtree_simple_xattr_cmp(name, *rbp); + if (ret < 0) + rbp =3D &(*rbp)->rb_left; + else if (ret > 0) + rbp =3D &(*rbp)->rb_right; + else + old_xattr =3D rb_entry(*rbp, struct simple_xattr, + rb_node); + if (old_xattr) + break; } =20 - /* - * If XATTR_CREATE or no flags are specified and neither an - * old or new xattr exist then we don't need to do anything. - */ - } + if (old_xattr) { + /* Fail if XATTR_CREATE is requested and the xattr exists. */ + if (flags & XATTR_CREATE) { + err =3D -EEXIST; + goto out_unlock; + } + + if (new_xattr) + rb_replace_node(&old_xattr->rb_node, + &new_xattr->rb_node, + &xattrs->rb_root); + else + rb_erase(&old_xattr->rb_node, + &xattrs->rb_root); + } else { + /* Fail if XATTR_REPLACE is requested but no xattr is found. */ + if (flags & XATTR_REPLACE) { + err =3D -ENODATA; + goto out_unlock; + } + + /* + * If XATTR_CREATE or no flags are specified together + * with a new value simply insert it. + */ + if (new_xattr) { + rb_link_node(&new_xattr->rb_node, parent, rbp); + rb_insert_color(&new_xattr->rb_node, + &xattrs->rb_root); + } + + /* + * If XATTR_CREATE or no flags are specified and + * neither an old or new xattr exist then we don't + * need to do anything. + */ + } =20 out_unlock: - write_unlock(&xattrs->lock); - if (!err) - return old_xattr; - simple_xattr_free(new_xattr); - return ERR_PTR(err); + write_unlock(&xattrs->lock); + if (err) + return ERR_PTR(err); + } + retain_and_null_ptr(new_xattr); + return old_xattr; } =20 static bool xattr_is_trusted(const char *name) @@ -1467,7 +1573,6 @@ ssize_t simple_xattr_list(struct inode *inode, struct= simple_xattrs *xattrs, { bool trusted =3D ns_capable_noaudit(&init_user_ns, CAP_SYS_ADMIN); struct simple_xattr *xattr; - struct rb_node *rbp; ssize_t remaining_size =3D size; int err =3D 0; =20 @@ -1487,23 +1592,62 @@ ssize_t simple_xattr_list(struct inode *inode, stru= ct simple_xattrs *xattrs, remaining_size -=3D err; err =3D 0; =20 - read_lock(&xattrs->lock); - for (rbp =3D rb_first(&xattrs->rb_root); rbp; rbp =3D rb_next(rbp)) { - xattr =3D rb_entry(rbp, struct simple_xattr, rb_node); + if (!xattrs) + return size - remaining_size; =20 - /* skip "trusted." attributes for unprivileged callers */ - if (!trusted && xattr_is_trusted(xattr->name)) - continue; + if (xattrs->use_rhashtable) { + struct rhashtable_iter iter; =20 - /* skip MAC labels; these are provided by LSM above */ - if (xattr_is_maclabel(xattr->name)) - continue; + rhashtable_walk_enter(&xattrs->ht, &iter); + rhashtable_walk_start(&iter); =20 - err =3D xattr_list_one(&buffer, &remaining_size, xattr->name); - if (err) - break; + while ((xattr =3D rhashtable_walk_next(&iter)) !=3D NULL) { + if (IS_ERR(xattr)) { + if (PTR_ERR(xattr) =3D=3D -EAGAIN) + continue; + err =3D PTR_ERR(xattr); + break; + } + + /* skip "trusted." attributes for unprivileged callers */ + if (!trusted && xattr_is_trusted(xattr->name)) + continue; + + /* skip MAC labels; these are provided by LSM above */ + if (xattr_is_maclabel(xattr->name)) + continue; + + err =3D xattr_list_one(&buffer, &remaining_size, + xattr->name); + if (err) + break; + } + + rhashtable_walk_stop(&iter); + rhashtable_walk_exit(&iter); + } else { + struct rb_node *rbp; + + read_lock(&xattrs->lock); + for (rbp =3D rb_first(&xattrs->rb_root); rbp; + rbp =3D rb_next(rbp)) { + xattr =3D rb_entry(rbp, struct simple_xattr, rb_node); + + /* skip "trusted." attributes for unprivileged callers */ + if (!trusted && xattr_is_trusted(xattr->name)) + continue; + + /* skip MAC labels; these are provided by LSM above */ + if (xattr_is_maclabel(xattr->name)) + continue; + + err =3D xattr_list_one(&buffer, &remaining_size, + xattr->name); + if (err) + break; + } + read_unlock(&xattrs->lock); } - read_unlock(&xattrs->lock); =20 return err ? err : size - remaining_size; } @@ -1536,9 +1680,16 @@ static bool rbtree_simple_xattr_less(struct rb_node = *new_node, void simple_xattr_add(struct simple_xattrs *xattrs, struct simple_xattr *new_xattr) { - write_lock(&xattrs->lock); - rb_add(&new_xattr->rb_node, &xattrs->rb_root, rbtree_simple_xattr_less); - write_unlock(&xattrs->lock); + if (xattrs->use_rhashtable) { + WARN_ON(rhashtable_insert_fast(&xattrs->ht, + &new_xattr->hash_node, + simple_xattr_params)); + } else { + write_lock(&xattrs->lock); + rb_add(&new_xattr->rb_node, &xattrs->rb_root, + rbtree_simple_xattr_less); + write_unlock(&xattrs->lock); + } } =20 /** @@ -1549,10 +1700,80 @@ void simple_xattr_add(struct simple_xattrs *xattrs, */ void simple_xattrs_init(struct simple_xattrs *xattrs) { + xattrs->use_rhashtable =3D false; xattrs->rb_root =3D RB_ROOT; rwlock_init(&xattrs->lock); } =20 +/** + * simple_xattrs_alloc - allocate and initialize a new xattr header + * + * Dynamically allocate a simple_xattrs header and initialize the + * underlying rhashtable. This is intended for consumers that want + * rhashtable-based xattr storage. + * + * Return: On success a new simple_xattrs is returned. On failure an + * ERR_PTR is returned. + */ +struct simple_xattrs *simple_xattrs_alloc(void) +{ + struct simple_xattrs *xattrs __free(kfree) =3D NULL; + + xattrs =3D kzalloc(sizeof(*xattrs), GFP_KERNEL); + if (!xattrs) + return ERR_PTR(-ENOMEM); + + xattrs->use_rhashtable =3D true; + if (rhashtable_init(&xattrs->ht, &simple_xattr_params)) + return ERR_PTR(-ENOMEM); + + return no_free_ptr(xattrs); +} + +/** + * simple_xattrs_lazy_alloc - get or allocate xattrs for a set operation + * @xattrsp: pointer to the xattrs pointer (may point to NULL) + * @value: value being set (NULL means remove) + * @flags: xattr set flags + * + * For lazily-allocated xattrs on the write path. If no xattrs exist yet + * and this is a remove operation, returns the appropriate result without + * allocating. Otherwise ensures xattrs is allocated and published with + * store-release semantics. + * + * Return: On success a valid pointer to the xattrs is returned. On + * failure or early-exit an ERR_PTR or NULL is returned. Callers should + * check with IS_ERR_OR_NULL() and propagate with PTR_ERR() which + * correctly returns 0 for the NULL no-op case. + */ +struct simple_xattrs *simple_xattrs_lazy_alloc(struct simple_xattrs **xatt= rsp, + const void *value, int flags) +{ + struct simple_xattrs *xattrs; + + xattrs =3D READ_ONCE(*xattrsp); + if (xattrs) + return xattrs; + + if (!value) + return (flags & XATTR_REPLACE) ? ERR_PTR(-ENODATA) : NULL; + + xattrs =3D simple_xattrs_alloc(); + if (!IS_ERR(xattrs)) + smp_store_release(xattrsp, xattrs); + return xattrs; +} + +static void simple_xattr_ht_free(void *ptr, void *arg) +{ + struct simple_xattr *xattr =3D ptr; + size_t *freed_space =3D arg; + + if (freed_space) + *freed_space +=3D simple_xattr_space(xattr->name, xattr->size); + simple_xattr_free(xattr); +} + /** * simple_xattrs_free - free xattrs * @xattrs: xattr header whose xattrs to destroy @@ -1563,22 +1784,28 @@ void simple_xattrs_init(struct simple_xattrs *xattr= s) */ void simple_xattrs_free(struct simple_xattrs *xattrs, size_t *freed_space) { - struct rb_node *rbp; - if (freed_space) *freed_space =3D 0; - rbp =3D rb_first(&xattrs->rb_root); - while (rbp) { - struct simple_xattr *xattr; - struct rb_node *rbp_next; - - rbp_next =3D rb_next(rbp); - xattr =3D rb_entry(rbp, struct simple_xattr, rb_node); - rb_erase(&xattr->rb_node, &xattrs->rb_root); - if (freed_space) - *freed_space +=3D simple_xattr_space(xattr->name, - xattr->size); - simple_xattr_free(xattr); - rbp =3D rbp_next; + + if (xattrs->use_rhashtable) { + rhashtable_free_and_destroy(&xattrs->ht, + simple_xattr_ht_free, freed_space); + } else { + struct rb_node *rbp; + + rbp =3D rb_first(&xattrs->rb_root); + while (rbp) { + struct simple_xattr *xattr; + struct rb_node *rbp_next; + + rbp_next =3D rb_next(rbp); + xattr =3D rb_entry(rbp, struct simple_xattr, rb_node); + rb_erase(&xattr->rb_node, &xattrs->rb_root); + if (freed_space) + *freed_space +=3D simple_xattr_space(xattr->name, + xattr->size); + simple_xattr_free(xattr); + rbp =3D rbp_next; + } } } diff --git a/include/linux/xattr.h b/include/linux/xattr.h index 1328f2bfd2ce..ee4fd40717a0 100644 --- a/include/linux/xattr.h +++ b/include/linux/xattr.h @@ -107,8 +107,14 @@ static inline const char *xattr_prefix(const struct xa= ttr_handler *handler) } =20 struct simple_xattrs { - struct rb_root rb_root; - rwlock_t lock; + bool use_rhashtable; + union { + struct { + struct rb_root rb_root; + rwlock_t lock; + }; + struct rhashtable ht; + }; }; =20 struct simple_xattr { @@ -121,6 +127,9 @@ struct simple_xattr { }; =20 void simple_xattrs_init(struct simple_xattrs *xattrs); +struct simple_xattrs *simple_xattrs_alloc(void); +struct simple_xattrs *simple_xattrs_lazy_alloc(struct simple_xattrs **xatt= rsp, + const void *value, int flags); void simple_xattrs_free(struct simple_xattrs *xattrs, size_t *freed_space); size_t simple_xattr_space(const char *name, size_t size); struct simple_xattr *simple_xattr_alloc(const void *value, size_t size); @@ -137,4 +146,16 @@ void simple_xattr_add(struct simple_xattrs *xattrs, struct simple_xattr *new_xattr); int xattr_list_one(char **buffer, ssize_t *remaining_size, const char *nam= e); =20 +DEFINE_CLASS(simple_xattr, + struct simple_xattr *, + if (!IS_ERR_OR_NULL(_T)) simple_xattr_free(_T), + simple_xattr_alloc(value, size), + const void *value, size_t size) + +DEFINE_CLASS(simple_xattrs, + struct simple_xattrs *, + if (!IS_ERR_OR_NULL(_T)) { simple_xattrs_free(_T, NULL); kfree= (_T); }, + simple_xattrs_alloc(), + void) + #endif /* _LINUX_XATTR_H */ diff --git a/mm/shmem.c b/mm/shmem.c index 063b4c3e4ccb..fc8020ce2e9f 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -4293,7 +4293,7 @@ static int shmem_initxattrs(struct inode *inode, =20 for (xattr =3D xattr_array; xattr->name !=3D NULL; xattr++) { new_xattr =3D simple_xattr_alloc(xattr->value, xattr->value_len); - if (!new_xattr) + if (IS_ERR(new_xattr)) break; =20 len =3D strlen(xattr->name) + 1; --=20 2.47.3 From nobody Thu Mar 19 02:04:58 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id F40DA30EF97; Mon, 16 Feb 2026 13:32:29 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771248750; cv=none; b=jaJ4zDw9YgiXGVLXZVL9HvLmdF7oIeKMvj4emvWKX5oIIDfoVfgtIcQxc+bDR7gQwx3Vq4yrS0fGw2VhY0ZONfPurq1CuKmPYH4LEVLAc+0WZiQ7Pz+s6r6AUyicYKoPf8Fy5nAkpoaoMxpo0K+vdeJP22a3tc6MZQzcr7ND1tE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771248750; c=relaxed/simple; bh=m6olJ1Aiin2fbb8rm99iZWNuSO99GG/M+f4atiTlDsM=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=hyLnELF3uefAKbMbgu+2bFWGzB7tzI44fc3wm+8Mf6lITnOEyWhUUuy9wx3muEFOvMRjow1rZGon/67TkdSVOPlJNbLBH+1GJukQuT/90m88lefq4LfWY2nqPdSgn25V6gQqWgIEg9eF85qhEgarvqjSy3rUHM6/Vd1//QA1g1U= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=gPG/x2PO; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="gPG/x2PO" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 49EFEC2BC9E; Mon, 16 Feb 2026 13:32:26 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1771248749; bh=m6olJ1Aiin2fbb8rm99iZWNuSO99GG/M+f4atiTlDsM=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=gPG/x2POnmd4b06vljpEiGw7e59h+pYTmb9s4b5qFyux5J9m4JQtKlmZKVsLLw+gg Tt4nDAlCxEFQLmOaF5g+7xFDRotxNrma1plzKXtrMlfVJA4MKdWdHc9Cx2lZyWDUwc 4mDnwpPXF27lZ0RaX160QgrHS5ccJY8U3gh25nWDXtOS2PoAZxFR3JPWChdOon0yn2 dkJ6LPPEpOGd5Nywf6PmsHqNTogFPNDaIVZ2AqUufHRNru3uNtEYJeqIMvi9NAHPj9 mYfu9qzicHzMWQ593fE1sCWOOt5Fjk36jfvUPHQaGTWLcxblguHqOMjiJJfVN/997n DTUmOtAy7mYrg== From: Christian Brauner Date: Mon, 16 Feb 2026 14:31:59 +0100 Subject: [PATCH 03/14] shmem: adapt to rhashtable-based simple_xattrs with lazy allocation Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260216-work-xattr-socket-v1-3-c2efa4f74cb7@kernel.org> References: <20260216-work-xattr-socket-v1-0-c2efa4f74cb7@kernel.org> In-Reply-To: <20260216-work-xattr-socket-v1-0-c2efa4f74cb7@kernel.org> To: linux-fsdevel@vger.kernel.org Cc: Jeff Layton , Josef Bacik , Alexander Viro , Jan Kara , linux-kernel@vger.kernel.org, Hugh Dickins , linux-mm@kvack.org, Greg Kroah-Hartman , Tejun Heo , Eric Dumazet , Jakub Kicinski , Jann Horn , netdev@vger.kernel.org, Christian Brauner X-Mailer: b4 0.15-dev-47773 X-Developer-Signature: v=1; a=openpgp-sha256; l=8768; i=brauner@kernel.org; h=from:subject:message-id; bh=m6olJ1Aiin2fbb8rm99iZWNuSO99GG/M+f4atiTlDsM=; b=owGbwMvMwCU28Zj0gdSKO4sYT6slMWROlolTFd/15cyltedzn5S1P/9iPSX13s9zVXoTd2Q3b L/b8e5lUEcpC4MYF4OsmCKLQ7tJuNxynorNRpkaMHNYmUCGMHBxCsBE4g4x/JVYvCl5aWS7+zGj yvajU3lqnQOtH0+cbXIq2tSQ88Bm3nWMDN/k4vSdlk9uWZp1Lyfz9YYTN3bLdIbbMc/57H2wRK/ nBiMA X-Developer-Key: i=brauner@kernel.org; a=openpgp; fpr=4880B8C9BD0E5106FC070F4F7B3C391EFEA93624 Adapt tmpfs/shmem to use the rhashtable-based xattr path and switch from an embedded struct to pointer-based lazy allocation. Change shmem_inode_info.xattrs from embedded 'struct simple_xattrs' to a pointer 'struct simple_xattrs *', initialized to NULL. This avoids the rhashtable overhead for every tmpfs inode, which helps when a lot of inodes exist. The xattr store is allocated on first use: - shmem_initxattrs(): Allocates via simple_xattrs_alloc() when security modules set initial xattrs during inode creation. - shmem_xattr_handler_set(): Allocates on first setxattr, with a short-circuit for removal when no xattrs are stored yet. All read paths (shmem_xattr_handler_get, shmem_listxattr) check for NULL xattrs pointer and return -ENODATA or 0 respectively. Replaced xattr entries are freed via simple_xattr_free_rcu() to allow concurrent RCU readers to finish. shmem_evict_inode() conditionally frees the xattr store only when allocated. Also change simple_xattr_add() from void to int to propagate rhashtable insertion failures. shmem_initxattrs() is the only caller. Signed-off-by: Christian Brauner Acked-by: "Darrick J. Wong" Reviewed-by: Jan Kara Reviewed-by: Jeff Layton --- fs/xattr.c | 26 +++++++++++++------------- include/linux/shmem_fs.h | 2 +- include/linux/xattr.h | 4 ++-- mm/shmem.c | 44 +++++++++++++++++++++++++++++++------------- 4 files changed, 47 insertions(+), 29 deletions(-) diff --git a/fs/xattr.c b/fs/xattr.c index 1d98ea459b7b..eb45ae0fd17f 100644 --- a/fs/xattr.c +++ b/fs/xattr.c @@ -1677,19 +1677,19 @@ static bool rbtree_simple_xattr_less(struct rb_node= *new_node, * of matching xattrs is wanted. Should only be called during inode * initialization when a few distinct initial xattrs are supposed to be se= t. */ -void simple_xattr_add(struct simple_xattrs *xattrs, - struct simple_xattr *new_xattr) -{ - if (xattrs->use_rhashtable) { - WARN_ON(rhashtable_insert_fast(&xattrs->ht, - &new_xattr->hash_node, - simple_xattr_params)); - } else { - write_lock(&xattrs->lock); - rb_add(&new_xattr->rb_node, &xattrs->rb_root, - rbtree_simple_xattr_less); - write_unlock(&xattrs->lock); - } +int simple_xattr_add(struct simple_xattrs *xattrs, + struct simple_xattr *new_xattr) +{ + if (xattrs->use_rhashtable) + return rhashtable_insert_fast(&xattrs->ht, + &new_xattr->hash_node, + simple_xattr_params); + + write_lock(&xattrs->lock); + rb_add(&new_xattr->rb_node, &xattrs->rb_root, + rbtree_simple_xattr_less); + write_unlock(&xattrs->lock); + return 0; } =20 /** diff --git a/include/linux/shmem_fs.h b/include/linux/shmem_fs.h index e2069b3179c4..53d325409a8b 100644 --- a/include/linux/shmem_fs.h +++ b/include/linux/shmem_fs.h @@ -48,7 +48,7 @@ struct shmem_inode_info { }; struct timespec64 i_crtime; /* file creation time */ struct shared_policy policy; /* NUMA memory alloc policy */ - struct simple_xattrs xattrs; /* list of xattrs */ + struct simple_xattrs *xattrs; /* list of xattrs */ pgoff_t fallocend; /* highest fallocate endindex */ unsigned int fsflags; /* for FS_IOC_[SG]ETFLAGS */ atomic_t stop_eviction; /* hold when working on inode */ diff --git a/include/linux/xattr.h b/include/linux/xattr.h index ee4fd40717a0..3063ecf0004d 100644 --- a/include/linux/xattr.h +++ b/include/linux/xattr.h @@ -142,8 +142,8 @@ struct simple_xattr *simple_xattr_set(struct simple_xat= trs *xattrs, size_t size, int flags); ssize_t simple_xattr_list(struct inode *inode, struct simple_xattrs *xattr= s, char *buffer, size_t size); -void simple_xattr_add(struct simple_xattrs *xattrs, - struct simple_xattr *new_xattr); +int simple_xattr_add(struct simple_xattrs *xattrs, + struct simple_xattr *new_xattr); int xattr_list_one(char **buffer, ssize_t *remaining_size, const char *nam= e); =20 DEFINE_CLASS(simple_xattr, diff --git a/mm/shmem.c b/mm/shmem.c index fc8020ce2e9f..8761c9b4f1c5 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -1426,7 +1426,10 @@ static void shmem_evict_inode(struct inode *inode) } } =20 - simple_xattrs_free(&info->xattrs, sbinfo->max_inodes ? &freed : NULL); + if (info->xattrs) { + simple_xattrs_free(info->xattrs, sbinfo->max_inodes ? &freed : NULL); + kfree(info->xattrs); + } shmem_free_inode(inode->i_sb, freed); WARN_ON(inode->i_blocks); clear_inode(inode); @@ -3118,7 +3121,6 @@ static struct inode *__shmem_get_inode(struct mnt_idm= ap *idmap, shmem_set_inode_flags(inode, info->fsflags, NULL); INIT_LIST_HEAD(&info->shrinklist); INIT_LIST_HEAD(&info->swaplist); - simple_xattrs_init(&info->xattrs); cache_no_acl(inode); if (sbinfo->noswap) mapping_set_unevictable(inode->i_mapping); @@ -4270,10 +4272,13 @@ static int shmem_initxattrs(struct inode *inode, struct shmem_inode_info *info =3D SHMEM_I(inode); struct shmem_sb_info *sbinfo =3D SHMEM_SB(inode->i_sb); const struct xattr *xattr; - struct simple_xattr *new_xattr; size_t ispace =3D 0; size_t len; =20 + CLASS(simple_xattrs, xattrs)(); + if (IS_ERR(xattrs)) + return PTR_ERR(xattrs); + if (sbinfo->max_inodes) { for (xattr =3D xattr_array; xattr->name !=3D NULL; xattr++) { ispace +=3D simple_xattr_space(xattr->name, @@ -4292,24 +4297,24 @@ static int shmem_initxattrs(struct inode *inode, } =20 for (xattr =3D xattr_array; xattr->name !=3D NULL; xattr++) { - new_xattr =3D simple_xattr_alloc(xattr->value, xattr->value_len); + CLASS(simple_xattr, new_xattr)(xattr->value, xattr->value_len); if (IS_ERR(new_xattr)) break; =20 len =3D strlen(xattr->name) + 1; new_xattr->name =3D kmalloc(XATTR_SECURITY_PREFIX_LEN + len, GFP_KERNEL_ACCOUNT); - if (!new_xattr->name) { - kvfree(new_xattr); + if (!new_xattr->name) break; - } =20 memcpy(new_xattr->name, XATTR_SECURITY_PREFIX, XATTR_SECURITY_PREFIX_LEN); memcpy(new_xattr->name + XATTR_SECURITY_PREFIX_LEN, xattr->name, len); =20 - simple_xattr_add(&info->xattrs, new_xattr); + if (simple_xattr_add(xattrs, new_xattr)) + break; + retain_and_null_ptr(new_xattr); } =20 if (xattr->name !=3D NULL) { @@ -4318,10 +4323,10 @@ static int shmem_initxattrs(struct inode *inode, sbinfo->free_ispace +=3D ispace; raw_spin_unlock(&sbinfo->stat_lock); } - simple_xattrs_free(&info->xattrs, NULL); return -ENOMEM; } =20 + smp_store_release(&info->xattrs, no_free_ptr(xattrs)); return 0; } =20 @@ -4330,9 +4335,14 @@ static int shmem_xattr_handler_get(const struct xatt= r_handler *handler, const char *name, void *buffer, size_t size) { struct shmem_inode_info *info =3D SHMEM_I(inode); + struct simple_xattrs *xattrs; + + xattrs =3D READ_ONCE(info->xattrs); + if (!xattrs) + return -ENODATA; =20 name =3D xattr_full_name(handler, name); - return simple_xattr_get(&info->xattrs, name, buffer, size); + return simple_xattr_get(xattrs, name, buffer, size); } =20 static int shmem_xattr_handler_set(const struct xattr_handler *handler, @@ -4343,10 +4353,16 @@ static int shmem_xattr_handler_set(const struct xat= tr_handler *handler, { struct shmem_inode_info *info =3D SHMEM_I(inode); struct shmem_sb_info *sbinfo =3D SHMEM_SB(inode->i_sb); + struct simple_xattrs *xattrs; struct simple_xattr *old_xattr; size_t ispace =3D 0; =20 name =3D xattr_full_name(handler, name); + + xattrs =3D simple_xattrs_lazy_alloc(&info->xattrs, value, flags); + if (IS_ERR_OR_NULL(xattrs)) + return PTR_ERR(xattrs); + if (value && sbinfo->max_inodes) { ispace =3D simple_xattr_space(name, size); raw_spin_lock(&sbinfo->stat_lock); @@ -4359,13 +4375,13 @@ static int shmem_xattr_handler_set(const struct xat= tr_handler *handler, return -ENOSPC; } =20 - old_xattr =3D simple_xattr_set(&info->xattrs, name, value, size, flags); + old_xattr =3D simple_xattr_set(xattrs, name, value, size, flags); if (!IS_ERR(old_xattr)) { ispace =3D 0; if (old_xattr && sbinfo->max_inodes) ispace =3D simple_xattr_space(old_xattr->name, old_xattr->size); - simple_xattr_free(old_xattr); + simple_xattr_free_rcu(old_xattr); old_xattr =3D NULL; inode_set_ctime_current(inode); inode_inc_iversion(inode); @@ -4406,7 +4422,9 @@ static const struct xattr_handler * const shmem_xattr= _handlers[] =3D { static ssize_t shmem_listxattr(struct dentry *dentry, char *buffer, size_t= size) { struct shmem_inode_info *info =3D SHMEM_I(d_inode(dentry)); - return simple_xattr_list(d_inode(dentry), &info->xattrs, buffer, size); + + return simple_xattr_list(d_inode(dentry), READ_ONCE(info->xattrs), + buffer, size); } #endif /* CONFIG_TMPFS_XATTR */ =20 --=20 2.47.3 From nobody Thu Mar 19 02:04:58 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7007230ACFF; Mon, 16 Feb 2026 13:32:33 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771248753; cv=none; b=MXwRDT8SYdtJ3PAiUfbJImPswjOQeukzgyRmduZGLjCuu+h2z0m4pMSCPvwqgVW2ANbzDQJm0nX56FrDopSpAnLE9kbMBIwfOM/CzzULg8zutArb4hmFdYSVBLHN7dNFPev+LvT6ZBbo1kYAJmy0nk/+FqXeVSIkfJRqlw7HTLk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771248753; c=relaxed/simple; bh=eNwIVI0p49LTJ3RM4G3gsM8NUnzeeg8PJjJ6WiNm1Tk=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=tuS+h3Ch2XQA68V1L6ej6GOZ7o0axBihbQPczI5kdtj0hiDM+yWOH9kysj5NqxOOT3CLryIl4BLVSbUGJswhlyeWYrBxOqpqs/2gs1sdP0m3qpSkOI6ONqbxSdDla9X4iX9jV25Wpt9UH6oRwYm4XMXHSWXkx7o2gvoW+Zs5xV8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=h0hjoQA7; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="h0hjoQA7" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 0F471C19424; Mon, 16 Feb 2026 13:32:29 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1771248753; bh=eNwIVI0p49LTJ3RM4G3gsM8NUnzeeg8PJjJ6WiNm1Tk=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=h0hjoQA7I9EL5Q3WT1FOglHuQxa2kzLf4syPEH/qZTg8wH09A53Jgwcmti7giUaki QXx1VmTmr5eBnYaL9MhvBpqjZb40AiS7nDPgg5BclHkp5y44XH5fdwIA6Ap4lQbs6c f9L+bqfmVMtJDWF6CekrukBUTU32y1AngxBMBPLqsiXgBHlcNiQTnH68JQMxl9e45m P8knq8EAcjIK/ZirgcGO0n6OR8dHxAcWlifBnq9N0B0HjaOEkIldzw3EoDJ13JKaOv DU22jqn3TFCFem7mXmbJIBodfNdn0U7kGu7nNshvhOyRm05I14rvLFYqaO7AR+hx1N vzVRcREh1hmeA== From: Christian Brauner Date: Mon, 16 Feb 2026 14:32:00 +0100 Subject: [PATCH 04/14] kernfs: adapt to rhashtable-based simple_xattrs with lazy allocation Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260216-work-xattr-socket-v1-4-c2efa4f74cb7@kernel.org> References: <20260216-work-xattr-socket-v1-0-c2efa4f74cb7@kernel.org> In-Reply-To: <20260216-work-xattr-socket-v1-0-c2efa4f74cb7@kernel.org> To: linux-fsdevel@vger.kernel.org Cc: Jeff Layton , Josef Bacik , Alexander Viro , Jan Kara , linux-kernel@vger.kernel.org, Hugh Dickins , linux-mm@kvack.org, Greg Kroah-Hartman , Tejun Heo , Eric Dumazet , Jakub Kicinski , Jann Horn , netdev@vger.kernel.org, Christian Brauner X-Mailer: b4 0.15-dev-47773 X-Developer-Signature: v=1; a=openpgp-sha256; l=6232; i=brauner@kernel.org; h=from:subject:message-id; bh=eNwIVI0p49LTJ3RM4G3gsM8NUnzeeg8PJjJ6WiNm1Tk=; b=owGbwMvMwCU28Zj0gdSKO4sYT6slMWROlomT7ZjzLc5yvpfrvu0r/+Z/eB20Jvu+pvVEObVPl /l2nxQu6ChlYRDjYpAVU2RxaDcJl1vOU7HZKFMDZg4rE8gQBi5OAZiIWw7Df5fidafm2ui9W37u 9/LQxaxT1565fITzBtv7ahtDh6eb5n9jZPj2zKlncsQl+WVPD5t7Mem4ihhy3fxy8NS6V4nL+zK PT2UFAA== X-Developer-Key: i=brauner@kernel.org; a=openpgp; fpr=4880B8C9BD0E5106FC070F4F7B3C391EFEA93624 Adapt kernfs to use the rhashtable-based xattr path and switch from an embedded struct to pointer-based lazy allocation. Change kernfs_iattrs.xattrs from embedded 'struct simple_xattrs' to a pointer 'struct simple_xattrs *', initialized to NULL (zeroed by kmem_cache_zalloc). Since kernfs_iattrs is already lazily allocated itself, this adds a second level of lazy allocation specifically for the xattr store. The xattr store is allocated on first setxattr. Read paths check for NULL and return -ENODATA or empty list. Replaced xattr entries are freed via simple_xattr_free_rcu() to allow concurrent RCU readers to finish. The cleanup paths in kernfs_free_rcu() and __kernfs_new_node() error handling conditionally free the xattr store only when allocated. Signed-off-by: Christian Brauner Acked-by: "Darrick J. Wong" Reviewed-by: Jan Kara Reviewed-by: Jeff Layton --- fs/kernfs/dir.c | 15 +++++++++++---- fs/kernfs/inode.c | 34 +++++++++++++++++++++++++--------- fs/kernfs/kernfs-internal.h | 2 +- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c index 29baeeb97871..e5735c45fb99 100644 --- a/fs/kernfs/dir.c +++ b/fs/kernfs/dir.c @@ -547,10 +547,8 @@ static void kernfs_free_rcu(struct rcu_head *rcu) /* If the whole node goes away, then name can't be used outside */ kfree_const(rcu_access_pointer(kn->name)); =20 - if (kn->iattr) { - simple_xattrs_free(&kn->iattr->xattrs, NULL); + if (kn->iattr) kmem_cache_free(kernfs_iattrs_cache, kn->iattr); - } =20 kmem_cache_free(kernfs_node_cache, kn); } @@ -584,6 +582,12 @@ void kernfs_put(struct kernfs_node *kn) if (kernfs_type(kn) =3D=3D KERNFS_LINK) kernfs_put(kn->symlink.target_kn); =20 + if (kn->iattr && kn->iattr->xattrs) { + simple_xattrs_free(kn->iattr->xattrs, NULL); + kfree(kn->iattr->xattrs); + kn->iattr->xattrs =3D NULL; + } + spin_lock(&root->kernfs_idr_lock); idr_remove(&root->ino_idr, (u32)kernfs_ino(kn)); spin_unlock(&root->kernfs_idr_lock); @@ -682,7 +686,10 @@ static struct kernfs_node *__kernfs_new_node(struct ke= rnfs_root *root, =20 err_out4: if (kn->iattr) { - simple_xattrs_free(&kn->iattr->xattrs, NULL); + if (kn->iattr->xattrs) { + simple_xattrs_free(kn->iattr->xattrs, NULL); + kfree(kn->iattr->xattrs); + } kmem_cache_free(kernfs_iattrs_cache, kn->iattr); } err_out3: diff --git a/fs/kernfs/inode.c b/fs/kernfs/inode.c index a36aaee98dce..dfc3315b5afc 100644 --- a/fs/kernfs/inode.c +++ b/fs/kernfs/inode.c @@ -45,7 +45,6 @@ static struct kernfs_iattrs *__kernfs_iattrs(struct kernf= s_node *kn, bool alloc) ret->ia_mtime =3D ret->ia_atime; ret->ia_ctime =3D ret->ia_atime; =20 - simple_xattrs_init(&ret->xattrs); atomic_set(&ret->nr_user_xattrs, 0); atomic_set(&ret->user_xattr_size, 0); =20 @@ -146,7 +145,8 @@ ssize_t kernfs_iop_listxattr(struct dentry *dentry, cha= r *buf, size_t size) if (!attrs) return -ENOMEM; =20 - return simple_xattr_list(d_inode(dentry), &attrs->xattrs, buf, size); + return simple_xattr_list(d_inode(dentry), READ_ONCE(attrs->xattrs), + buf, size); } =20 static inline void set_default_inode_attr(struct inode *inode, umode_t mod= e) @@ -298,27 +298,38 @@ int kernfs_xattr_get(struct kernfs_node *kn, const ch= ar *name, void *value, size_t size) { struct kernfs_iattrs *attrs =3D kernfs_iattrs_noalloc(kn); + struct simple_xattrs *xattrs; + if (!attrs) return -ENODATA; =20 - return simple_xattr_get(&attrs->xattrs, name, value, size); + xattrs =3D READ_ONCE(attrs->xattrs); + if (!xattrs) + return -ENODATA; + + return simple_xattr_get(xattrs, name, value, size); } =20 int kernfs_xattr_set(struct kernfs_node *kn, const char *name, const void *value, size_t size, int flags) { struct simple_xattr *old_xattr; + struct simple_xattrs *xattrs; struct kernfs_iattrs *attrs; =20 attrs =3D kernfs_iattrs(kn); if (!attrs) return -ENOMEM; =20 - old_xattr =3D simple_xattr_set(&attrs->xattrs, name, value, size, flags); + xattrs =3D simple_xattrs_lazy_alloc(&attrs->xattrs, value, flags); + if (IS_ERR_OR_NULL(xattrs)) + return PTR_ERR(xattrs); + + old_xattr =3D simple_xattr_set(xattrs, name, value, size, flags); if (IS_ERR(old_xattr)) return PTR_ERR(old_xattr); =20 - simple_xattr_free(old_xattr); + simple_xattr_free_rcu(old_xattr); return 0; } =20 @@ -376,7 +387,7 @@ static int kernfs_vfs_user_xattr_add(struct kernfs_node= *kn, =20 ret =3D 0; size =3D old_xattr->size; - simple_xattr_free(old_xattr); + simple_xattr_free_rcu(old_xattr); dec_size_out: atomic_sub(size, sz); dec_count_out: @@ -403,7 +414,7 @@ static int kernfs_vfs_user_xattr_rm(struct kernfs_node = *kn, =20 atomic_sub(old_xattr->size, sz); atomic_dec(nr); - simple_xattr_free(old_xattr); + simple_xattr_free_rcu(old_xattr); return 0; } =20 @@ -415,6 +426,7 @@ static int kernfs_vfs_user_xattr_set(const struct xattr= _handler *handler, { const char *full_name =3D xattr_full_name(handler, suffix); struct kernfs_node *kn =3D inode->i_private; + struct simple_xattrs *xattrs; struct kernfs_iattrs *attrs; =20 if (!(kernfs_root(kn)->flags & KERNFS_ROOT_SUPPORT_USER_XATTR)) @@ -424,11 +436,15 @@ static int kernfs_vfs_user_xattr_set(const struct xat= tr_handler *handler, if (!attrs) return -ENOMEM; =20 + xattrs =3D simple_xattrs_lazy_alloc(&attrs->xattrs, value, flags); + if (IS_ERR_OR_NULL(xattrs)) + return PTR_ERR(xattrs); + if (value) - return kernfs_vfs_user_xattr_add(kn, full_name, &attrs->xattrs, + return kernfs_vfs_user_xattr_add(kn, full_name, xattrs, value, size, flags); else - return kernfs_vfs_user_xattr_rm(kn, full_name, &attrs->xattrs, + return kernfs_vfs_user_xattr_rm(kn, full_name, xattrs, value, size, flags); =20 } diff --git a/fs/kernfs/kernfs-internal.h b/fs/kernfs/kernfs-internal.h index 6061b6f70d2a..1324ed8c0661 100644 --- a/fs/kernfs/kernfs-internal.h +++ b/fs/kernfs/kernfs-internal.h @@ -26,7 +26,7 @@ struct kernfs_iattrs { struct timespec64 ia_mtime; struct timespec64 ia_ctime; =20 - struct simple_xattrs xattrs; + struct simple_xattrs *xattrs; atomic_t nr_user_xattrs; atomic_t user_xattr_size; }; --=20 2.47.3 From nobody Thu Mar 19 02:04:58 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 61AD030ACFF; Mon, 16 Feb 2026 13:32:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771248757; cv=none; b=XAYYi08VI2xi78ZLf8Io2yg2rdXkRi7WUDm1t/AlMOdDwXTtjHZGU5cpvJE1x49p2cNw6H4HEeJEZzh/EIfBBoTbASZmEWJFkR8DR/utbZQ8qosFpOF6rBeDB2htdHNWamFj6qqNGjcm70Ya45IjOhfc9cE711fjJ4wnxy04BkQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771248757; c=relaxed/simple; bh=07ziX3ZBWcGP0yXHy4c2MCvyc7P5td2Iy64SBWqQAOo=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=uEKklVyRyuNcqIHfb/JLbaqFZ8O6asTEaBsK8OU+DXZTsLYzg70tN0plGy2ZOoIlm/zqyAglFtSbOz7CxRVwpIiyAbT+NzINpq8GOkHl2WSDmSs0VfPEcPnC8XTqR05VNhi+xEZKmP0/PjmcVobKlnRGwOdkGvNnPe8Hgx7YrqU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=m+BfJKAz; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="m+BfJKAz" Received: by smtp.kernel.org (Postfix) with ESMTPSA id D0B37C2BC87; Mon, 16 Feb 2026 13:32:33 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1771248756; bh=07ziX3ZBWcGP0yXHy4c2MCvyc7P5td2Iy64SBWqQAOo=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=m+BfJKAzYWN4zRNOyeAKZFgosWaCsFhQr5OoKBjrtGva6kKKZwcb4Xc0ENxQhwZxW Cav9RWXEa+GqmdMN4mjcEvTPYAQCyQdnVHTDK9l9ZeV1F5FvfS7/FWV+11RQzgpRkF 4rmR0JlI0gQAlZyfJfnpMvbYhHETArqW/CI4j4FDhOp6Pon5WyNlwzcYKDOM2vm5Qw SEqGiKWWZqeMDdbRHB4bAs1PJucx+LVd55gKbstS54PNXycvtc6h34CoTNbW4oQiJJ +gnSRdqpHPesrG/aDcLIUKe9KQh9Vqc8j76cTO1y+hKdouYYBRgMoTOHeZGvKG8jKv uOpT/x1XDiSQw== From: Christian Brauner Date: Mon, 16 Feb 2026 14:32:01 +0100 Subject: [PATCH 05/14] pidfs: adapt to rhashtable-based simple_xattrs Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260216-work-xattr-socket-v1-5-c2efa4f74cb7@kernel.org> References: <20260216-work-xattr-socket-v1-0-c2efa4f74cb7@kernel.org> In-Reply-To: <20260216-work-xattr-socket-v1-0-c2efa4f74cb7@kernel.org> To: linux-fsdevel@vger.kernel.org Cc: Jeff Layton , Josef Bacik , Alexander Viro , Jan Kara , linux-kernel@vger.kernel.org, Hugh Dickins , linux-mm@kvack.org, Greg Kroah-Hartman , Tejun Heo , Eric Dumazet , Jakub Kicinski , Jann Horn , netdev@vger.kernel.org, Christian Brauner X-Mailer: b4 0.15-dev-47773 X-Developer-Signature: v=1; a=openpgp-sha256; l=4671; i=brauner@kernel.org; h=from:subject:message-id; bh=07ziX3ZBWcGP0yXHy4c2MCvyc7P5td2Iy64SBWqQAOo=; b=owGbwMvMwCU28Zj0gdSKO4sYT6slMWROlonrmOSRt8BdI/uU0iNeMd706vvT+u6knXFNkOs8E xV/M0yqo5SFQYyLQVZMkcWh3SRcbjlPxWajTA2YOaxMIEMYuDgFYCL5rYwMO/OFTrtsmf/w+sqq mxcSshzvbYzPqk0T37/IOnD3e7FdQYwMZ+xlClubDmjJHnt5zIFpR9KCF1cXVz2Tecu5sTdn3e4 b7AA= X-Developer-Key: i=brauner@kernel.org; a=openpgp; fpr=4880B8C9BD0E5106FC070F4F7B3C391EFEA93624 Adapt pidfs to use the rhashtable-based xattr path by switching from a dedicated slab cache to simple_xattrs_alloc(). Previously pidfs used a custom kmem_cache (pidfs_xattr_cachep) that allocated a struct containing an embedded simple_xattrs plus simple_xattrs_init(). Replace this with simple_xattrs_alloc() which combines kzalloc + rhashtable_init, and drop the dedicated slab cache entirely. Use simple_xattr_free_rcu() for replaced xattr entries to allow concurrent RCU readers to finish. Signed-off-by: Christian Brauner Acked-by: "Darrick J. Wong" Reviewed-by: Jan Kara Reviewed-by: Jeff Layton --- fs/pidfs.c | 65 +++++++++++++++++++++++++++++++++++++++-------------------= ---- 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/fs/pidfs.c b/fs/pidfs.c index 1e20e36e0ed5..cb62000681df 100644 --- a/fs/pidfs.c +++ b/fs/pidfs.c @@ -21,6 +21,7 @@ #include #include #include +#include #include =20 #include "internal.h" @@ -29,7 +30,6 @@ #define PIDFS_PID_DEAD ERR_PTR(-ESRCH) =20 static struct kmem_cache *pidfs_attr_cachep __ro_after_init; -static struct kmem_cache *pidfs_xattr_cachep __ro_after_init; =20 static struct path pidfs_root_path =3D {}; =20 @@ -44,9 +44,8 @@ enum pidfs_attr_mask_bits { PIDFS_ATTR_BIT_COREDUMP =3D 1, }; =20 -struct pidfs_attr { +struct pidfs_anon_attr { unsigned long attr_mask; - struct simple_xattrs *xattrs; struct /* exit info */ { __u64 cgroupid; __s32 exit_code; @@ -55,6 +54,14 @@ struct pidfs_attr { __u32 coredump_signal; }; =20 +struct pidfs_attr { + struct simple_xattrs *xattrs; + union { + struct pidfs_anon_attr; + struct llist_node pidfs_llist; + }; +}; + static struct rb_root pidfs_ino_tree =3D RB_ROOT; =20 #if BITS_PER_LONG =3D=3D 32 @@ -147,10 +154,30 @@ void pidfs_remove_pid(struct pid *pid) write_seqcount_end(&pidmap_lock_seq); } =20 +static LLIST_HEAD(pidfs_free_list); + +static void pidfs_free_attr_work(struct work_struct *work) +{ + struct pidfs_attr *attr, *next; + struct llist_node *head; + + head =3D llist_del_all(&pidfs_free_list); + llist_for_each_entry_safe(attr, next, head, pidfs_llist) { + struct simple_xattrs *xattrs =3D attr->xattrs; + + if (xattrs) { + simple_xattrs_free(xattrs, NULL); + kfree(xattrs); + } + kfree(attr); + } +} + +static DECLARE_WORK(pidfs_free_work, pidfs_free_attr_work); + void pidfs_free_pid(struct pid *pid) { - struct pidfs_attr *attr __free(kfree) =3D no_free_ptr(pid->attr); - struct simple_xattrs *xattrs __free(kfree) =3D NULL; + struct pidfs_attr *attr =3D pid->attr; =20 /* * Any dentry must've been wiped from the pid by now. @@ -169,9 +196,10 @@ void pidfs_free_pid(struct pid *pid) if (IS_ERR(attr)) return; =20 - xattrs =3D no_free_ptr(attr->xattrs); - if (xattrs) - simple_xattrs_free(xattrs, NULL); + if (likely(!attr->xattrs)) + kfree(attr); + else if (llist_add(&attr->pidfs_llist, &pidfs_free_list)) + schedule_work(&pidfs_free_work); } =20 #ifdef CONFIG_PROC_FS @@ -998,7 +1026,7 @@ static int pidfs_xattr_get(const struct xattr_handler = *handler, =20 xattrs =3D READ_ONCE(attr->xattrs); if (!xattrs) - return 0; + return -ENODATA; =20 name =3D xattr_full_name(handler, suffix); return simple_xattr_get(xattrs, name, value, size); @@ -1018,22 +1046,16 @@ static int pidfs_xattr_set(const struct xattr_handl= er *handler, /* Ensure we're the only one to set @attr->xattrs. */ WARN_ON_ONCE(!inode_is_locked(inode)); =20 - xattrs =3D READ_ONCE(attr->xattrs); - if (!xattrs) { - xattrs =3D kmem_cache_zalloc(pidfs_xattr_cachep, GFP_KERNEL); - if (!xattrs) - return -ENOMEM; - - simple_xattrs_init(xattrs); - smp_store_release(&pid->attr->xattrs, xattrs); - } + xattrs =3D simple_xattrs_lazy_alloc(&attr->xattrs, value, flags); + if (IS_ERR_OR_NULL(xattrs)) + return PTR_ERR(xattrs); =20 name =3D xattr_full_name(handler, suffix); old_xattr =3D simple_xattr_set(xattrs, name, value, size, flags); if (IS_ERR(old_xattr)) return PTR_ERR(old_xattr); =20 - simple_xattr_free(old_xattr); + simple_xattr_free_rcu(old_xattr); return 0; } =20 @@ -1108,11 +1130,6 @@ void __init pidfs_init(void) (SLAB_HWCACHE_ALIGN | SLAB_RECLAIM_ACCOUNT | SLAB_ACCOUNT | SLAB_PANIC), NULL); =20 - pidfs_xattr_cachep =3D kmem_cache_create("pidfs_xattr_cache", - sizeof(struct simple_xattrs), 0, - (SLAB_HWCACHE_ALIGN | SLAB_RECLAIM_ACCOUNT | - SLAB_ACCOUNT | SLAB_PANIC), NULL); - pidfs_mnt =3D kern_mount(&pidfs_type); if (IS_ERR(pidfs_mnt)) panic("Failed to mount pidfs pseudo filesystem"); --=20 2.47.3 From nobody Thu Mar 19 02:04:58 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0C3FA30ACFF; Mon, 16 Feb 2026 13:32:40 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771248761; cv=none; b=SHINLEbcvG2xsYE0LZ3v7hsEV6DQxLovHLjUmBheiITdotGHhP4vRftyi1Q25WGOlTgNikWmNOKpW4j6FdKHC31waHroyO6hwgVOI3savwL4KRkrwADKjRN/w/iq997uM8bpS2N6LovErYeaKarTXhQVYd1+f2vzPm3jrXG0VUQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771248761; c=relaxed/simple; bh=LOjPXeWI/mcGWviTZGHLTBtGvdxahRz+Iq50IhxnB2w=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=NCDUtOQ6rmplYVNHXyY/aFBdMNM9R3aOYALmsjftJvu8BVEruT07Zg5b4PmINdIEx0+BoEx22LCuj+gBSXt8wpIX9yAIvYuPjeFvoXhUj0ws2Vtg3rxkQXmsh279UrYbVel/Tp8ScVTfeMfuifr6QiQ0cDAIXsVB4bFQ2BMCOL0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=Bu/E3n5d; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="Bu/E3n5d" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 65D0EC19424; Mon, 16 Feb 2026 13:32:37 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1771248760; bh=LOjPXeWI/mcGWviTZGHLTBtGvdxahRz+Iq50IhxnB2w=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=Bu/E3n5dCeLinC5+TWa9MsIr0Fi+4hOIX/rQLUJA33opzDTBW9daZUvHqgXADKUxe 7Vkbr2OXsSfztKXI4PBhzRjWpZ54lu6xMdpX3ItFXlI77vrdPxMHkYnUBeBut/rk83 xaYfyDXXBQMeKqZPyX0j7mlTo5H9TpINnHB0xa5NIeXKu3ZzmLokOzjdBPmEh7rnJK swSZKL/ypGT5sjy9XKverGepxRV9pPWs1UFMKAPQG6rovIPH3kOP5D+SxSm3L5qdZ8 dSATicb4Wtv+z51T0nDMuWgTUkHvDNwWFgwnScCOcAkNVF0f4a+vQsSm055cKwiMKb CSg4P7RHT1KXg== From: Christian Brauner Date: Mon, 16 Feb 2026 14:32:02 +0100 Subject: [PATCH 06/14] xattr: remove rbtree-based simple_xattr infrastructure Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260216-work-xattr-socket-v1-6-c2efa4f74cb7@kernel.org> References: <20260216-work-xattr-socket-v1-0-c2efa4f74cb7@kernel.org> In-Reply-To: <20260216-work-xattr-socket-v1-0-c2efa4f74cb7@kernel.org> To: linux-fsdevel@vger.kernel.org Cc: Jeff Layton , Josef Bacik , Alexander Viro , Jan Kara , linux-kernel@vger.kernel.org, Hugh Dickins , linux-mm@kvack.org, Greg Kroah-Hartman , Tejun Heo , Eric Dumazet , Jakub Kicinski , Jann Horn , netdev@vger.kernel.org, Christian Brauner X-Mailer: b4 0.15-dev-47773 X-Developer-Signature: v=1; a=openpgp-sha256; l=17508; i=brauner@kernel.org; h=from:subject:message-id; bh=LOjPXeWI/mcGWviTZGHLTBtGvdxahRz+Iq50IhxnB2w=; b=owGbwMvMwCU28Zj0gdSKO4sYT6slMWROlom7VJ0ScVs5SfpL8UW/9dve1TdXfhdk6bv+43/x1 gPtunP4OkpZGMS4GGTFFFkc2k3C5ZbzVGw2ytSAmcPKBDKEgYtTACaikMvwP8f3mlmhfdA7oZWK vM5G75ZOv5N493XNOQvtvvbDSyZ/eMzw3yFjVp8dwxalnLs5871/zj/E5GNRsq6kagVX15Jfuld CmQE= X-Developer-Key: i=brauner@kernel.org; a=openpgp; fpr=4880B8C9BD0E5106FC070F4F7B3C391EFEA93624 Now that all consumers (shmem, kernfs, pidfs) have been converted to use the rhashtable-based simple_xattrs with pointer-based lazy allocation, remove the legacy rbtree code path. The rhashtable implementation provides O(1) average-case lookup with RCU-based lockless reads, replacing the O(log n) rbtree with reader-writer spinlock contention. Signed-off-by: Christian Brauner Acked-by: "Darrick J. Wong" Reviewed-by: Jan Kara Reviewed-by: Jeff Layton --- fs/xattr.c | 387 +++++++++++++---------------------------------= ---- include/linux/xattr.h | 12 +- 2 files changed, 103 insertions(+), 296 deletions(-) diff --git a/fs/xattr.c b/fs/xattr.c index eb45ae0fd17f..64803097e1dc 100644 --- a/fs/xattr.c +++ b/fs/xattr.c @@ -1200,20 +1200,18 @@ void simple_xattr_free(struct simple_xattr *xattr) =20 static void simple_xattr_rcu_free(struct rcu_head *head) { - struct simple_xattr *xattr; + struct simple_xattr *xattr =3D container_of(head, struct simple_xattr, rc= u); =20 - xattr =3D container_of(head, struct simple_xattr, rcu); simple_xattr_free(xattr); } =20 /** - * simple_xattr_free_rcu - free an xattr object after an RCU grace period + * simple_xattr_free_rcu - free an xattr object with RCU delay * @xattr: the xattr object * - * Schedule RCU-deferred freeing of an xattr entry. This is used by - * rhashtable-based callers of simple_xattr_set() that replace or remove - * an existing entry while concurrent RCU readers may still be accessing - * it. + * Free the xattr object after an RCU grace period. This must be used when + * the xattr was removed from a data structure that concurrent RCU readers + * may still be traversing. Can handle @xattr being NULL. */ void simple_xattr_free_rcu(struct simple_xattr *xattr) { @@ -1254,43 +1252,6 @@ struct simple_xattr *simple_xattr_alloc(const void *= value, size_t size) return new_xattr; } =20 -/** - * rbtree_simple_xattr_cmp - compare xattr name with current rbtree xattr = entry - * @key: xattr name - * @node: current node - * - * Compare the xattr name with the xattr name attached to @node in the rbt= ree. - * - * Return: Negative value if continuing left, positive if continuing right= , 0 - * if the xattr attached to @node matches @key. - */ -static int rbtree_simple_xattr_cmp(const void *key, const struct rb_node *= node) -{ - const char *xattr_name =3D key; - const struct simple_xattr *xattr; - - xattr =3D rb_entry(node, struct simple_xattr, rb_node); - return strcmp(xattr->name, xattr_name); -} - -/** - * rbtree_simple_xattr_node_cmp - compare two xattr rbtree nodes - * @new_node: new node - * @node: current node - * - * Compare the xattr attached to @new_node with the xattr attached to @nod= e. - * - * Return: Negative value if continuing left, positive if continuing right= , 0 - * if the xattr attached to @new_node matches the xattr attached to @node. - */ -static int rbtree_simple_xattr_node_cmp(struct rb_node *new_node, - const struct rb_node *node) -{ - struct simple_xattr *xattr; - xattr =3D rb_entry(new_node, struct simple_xattr, rb_node); - return rbtree_simple_xattr_cmp(xattr->name, node); -} - static u32 simple_xattr_hashfn(const void *data, u32 len, u32 seed) { const char *name =3D data; @@ -1336,41 +1297,19 @@ static const struct rhashtable_params simple_xattr_= params =3D { int simple_xattr_get(struct simple_xattrs *xattrs, const char *name, void *buffer, size_t size) { - struct simple_xattr *xattr =3D NULL; + struct simple_xattr *xattr; int ret =3D -ENODATA; =20 - if (xattrs->use_rhashtable) { - guard(rcu)(); - xattr =3D rhashtable_lookup(&xattrs->ht, name, - simple_xattr_params); - if (xattr) { - ret =3D xattr->size; - if (buffer) { - if (size < xattr->size) - ret =3D -ERANGE; - else - memcpy(buffer, xattr->value, - xattr->size); - } - } - } else { - struct rb_node *rbp; - - read_lock(&xattrs->lock); - rbp =3D rb_find(name, &xattrs->rb_root, - rbtree_simple_xattr_cmp); - if (rbp) { - xattr =3D rb_entry(rbp, struct simple_xattr, rb_node); - ret =3D xattr->size; - if (buffer) { - if (size < xattr->size) - ret =3D -ERANGE; - else - memcpy(buffer, xattr->value, - xattr->size); - } + guard(rcu)(); + xattr =3D rhashtable_lookup(&xattrs->ht, name, simple_xattr_params); + if (xattr) { + ret =3D xattr->size; + if (buffer) { + if (size < xattr->size) + ret =3D -ERANGE; + else + memcpy(buffer, xattr->value, xattr->size); } - read_unlock(&xattrs->lock); } return ret; } @@ -1398,6 +1337,11 @@ int simple_xattr_get(struct simple_xattrs *xattrs, c= onst char *name, * nothing if XATTR_CREATE is specified in @flags or @flags is zero. For * XATTR_REPLACE we fail as mentioned above. * + * Note: Callers must externally serialize writes. All current callers hold + * the inode lock for write operations. The lookup->replace/remove sequence + * is not atomic with respect to the rhashtable's per-bucket locking, but + * is safe because writes are serialized by the caller. + * * Return: On success, the removed or replaced xattr is returned, to be fr= eed * by the caller; or NULL if none. On failure a negative error code is ret= urned. */ @@ -1406,7 +1350,7 @@ struct simple_xattr *simple_xattr_set(struct simple_x= attrs *xattrs, size_t size, int flags) { struct simple_xattr *old_xattr =3D NULL; - int err =3D 0; + int err; =20 CLASS(simple_xattr, new_xattr)(value, size); if (IS_ERR(new_xattr)) @@ -1418,119 +1362,52 @@ struct simple_xattr *simple_xattr_set(struct simpl= e_xattrs *xattrs, return ERR_PTR(-ENOMEM); } =20 - if (xattrs->use_rhashtable) { - /* - * Lookup is safe without RCU here since writes are - * serialized by the caller. - */ - old_xattr =3D rhashtable_lookup_fast(&xattrs->ht, name, - simple_xattr_params); - - if (old_xattr) { - /* Fail if XATTR_CREATE is requested and the xattr exists. */ - if (flags & XATTR_CREATE) - return ERR_PTR(-EEXIST); - - if (new_xattr) { - err =3D rhashtable_replace_fast(&xattrs->ht, - &old_xattr->hash_node, - &new_xattr->hash_node, - simple_xattr_params); - if (err) - return ERR_PTR(err); - } else { - err =3D rhashtable_remove_fast(&xattrs->ht, - &old_xattr->hash_node, - simple_xattr_params); - if (err) - return ERR_PTR(err); - } - } else { - /* Fail if XATTR_REPLACE is requested but no xattr is found. */ - if (flags & XATTR_REPLACE) - return ERR_PTR(-ENODATA); - - /* - * If XATTR_CREATE or no flags are specified together - * with a new value simply insert it. - */ - if (new_xattr) { - err =3D rhashtable_insert_fast(&xattrs->ht, - &new_xattr->hash_node, - simple_xattr_params); - if (err) - return ERR_PTR(err); - } - - /* - * If XATTR_CREATE or no flags are specified and - * neither an old or new xattr exist then we don't - * need to do anything. - */ - } - } else { - struct rb_node *parent =3D NULL, **rbp; - int ret; - - write_lock(&xattrs->lock); - rbp =3D &xattrs->rb_root.rb_node; - while (*rbp) { - parent =3D *rbp; - ret =3D rbtree_simple_xattr_cmp(name, *rbp); - if (ret < 0) - rbp =3D &(*rbp)->rb_left; - else if (ret > 0) - rbp =3D &(*rbp)->rb_right; - else - old_xattr =3D rb_entry(*rbp, struct simple_xattr, - rb_node); - if (old_xattr) - break; - } + /* Lookup is safe without RCU here since writes are serialized. */ + old_xattr =3D rhashtable_lookup_fast(&xattrs->ht, name, + simple_xattr_params); =20 - if (old_xattr) { - /* Fail if XATTR_CREATE is requested and the xattr exists. */ - if (flags & XATTR_CREATE) { - err =3D -EEXIST; - goto out_unlock; - } + if (old_xattr) { + /* Fail if XATTR_CREATE is requested and the xattr exists. */ + if (flags & XATTR_CREATE) + return ERR_PTR(-EEXIST); =20 - if (new_xattr) - rb_replace_node(&old_xattr->rb_node, - &new_xattr->rb_node, - &xattrs->rb_root); - else - rb_erase(&old_xattr->rb_node, - &xattrs->rb_root); + if (new_xattr) { + err =3D rhashtable_replace_fast(&xattrs->ht, + &old_xattr->hash_node, + &new_xattr->hash_node, + simple_xattr_params); + if (err) + return ERR_PTR(err); } else { - /* Fail if XATTR_REPLACE is requested but no xattr is found. */ - if (flags & XATTR_REPLACE) { - err =3D -ENODATA; - goto out_unlock; - } - - /* - * If XATTR_CREATE or no flags are specified together - * with a new value simply insert it. - */ - if (new_xattr) { - rb_link_node(&new_xattr->rb_node, parent, rbp); - rb_insert_color(&new_xattr->rb_node, - &xattrs->rb_root); - } + err =3D rhashtable_remove_fast(&xattrs->ht, + &old_xattr->hash_node, + simple_xattr_params); + if (err) + return ERR_PTR(err); + } + } else { + /* Fail if XATTR_REPLACE is requested but no xattr is found. */ + if (flags & XATTR_REPLACE) + return ERR_PTR(-ENODATA); =20 - /* - * If XATTR_CREATE or no flags are specified and - * neither an old or new xattr exist then we don't - * need to do anything. - */ + /* + * If XATTR_CREATE or no flags are specified together with a + * new value simply insert it. + */ + if (new_xattr) { + err =3D rhashtable_insert_fast(&xattrs->ht, + &new_xattr->hash_node, + simple_xattr_params); + if (err) + return ERR_PTR(err); } =20 -out_unlock: - write_unlock(&xattrs->lock); - if (err) - return ERR_PTR(err); + /* + * If XATTR_CREATE or no flags are specified and neither an + * old or new xattr exist then we don't need to do anything. + */ } + retain_and_null_ptr(new_xattr); return old_xattr; } @@ -1572,6 +1449,7 @@ ssize_t simple_xattr_list(struct inode *inode, struct= simple_xattrs *xattrs, char *buffer, size_t size) { bool trusted =3D ns_capable_noaudit(&init_user_ns, CAP_SYS_ADMIN); + struct rhashtable_iter iter; struct simple_xattr *xattr; ssize_t remaining_size =3D size; int err =3D 0; @@ -1595,77 +1473,34 @@ ssize_t simple_xattr_list(struct inode *inode, stru= ct simple_xattrs *xattrs, if (!xattrs) return size - remaining_size; =20 - if (xattrs->use_rhashtable) { - struct rhashtable_iter iter; - - rhashtable_walk_enter(&xattrs->ht, &iter); - rhashtable_walk_start(&iter); - - while ((xattr =3D rhashtable_walk_next(&iter)) !=3D NULL) { - if (IS_ERR(xattr)) { - if (PTR_ERR(xattr) =3D=3D -EAGAIN) - continue; - err =3D PTR_ERR(xattr); - break; - } - - /* skip "trusted." attributes for unprivileged callers */ - if (!trusted && xattr_is_trusted(xattr->name)) - continue; + rhashtable_walk_enter(&xattrs->ht, &iter); + rhashtable_walk_start(&iter); =20 - /* skip MAC labels; these are provided by LSM above */ - if (xattr_is_maclabel(xattr->name)) + while ((xattr =3D rhashtable_walk_next(&iter)) !=3D NULL) { + if (IS_ERR(xattr)) { + if (PTR_ERR(xattr) =3D=3D -EAGAIN) continue; - - err =3D xattr_list_one(&buffer, &remaining_size, - xattr->name); - if (err) - break; + err =3D PTR_ERR(xattr); + break; } =20 - rhashtable_walk_stop(&iter); - rhashtable_walk_exit(&iter); - } else { - struct rb_node *rbp; - - read_lock(&xattrs->lock); - for (rbp =3D rb_first(&xattrs->rb_root); rbp; - rbp =3D rb_next(rbp)) { - xattr =3D rb_entry(rbp, struct simple_xattr, rb_node); - - /* skip "trusted." attributes for unprivileged callers */ - if (!trusted && xattr_is_trusted(xattr->name)) - continue; + /* skip "trusted." attributes for unprivileged callers */ + if (!trusted && xattr_is_trusted(xattr->name)) + continue; =20 - /* skip MAC labels; these are provided by LSM above */ - if (xattr_is_maclabel(xattr->name)) - continue; + /* skip MAC labels; these are provided by LSM above */ + if (xattr_is_maclabel(xattr->name)) + continue; =20 - err =3D xattr_list_one(&buffer, &remaining_size, - xattr->name); - if (err) - break; - } - read_unlock(&xattrs->lock); + err =3D xattr_list_one(&buffer, &remaining_size, xattr->name); + if (err) + break; } =20 - return err ? err : size - remaining_size; -} + rhashtable_walk_stop(&iter); + rhashtable_walk_exit(&iter); =20 -/** - * rbtree_simple_xattr_less - compare two xattr rbtree nodes - * @new_node: new node - * @node: current node - * - * Compare the xattr attached to @new_node with the xattr attached to @nod= e. - * Note that this function technically tolerates duplicate entries. - * - * Return: True if insertion point in the rbtree is found. - */ -static bool rbtree_simple_xattr_less(struct rb_node *new_node, - const struct rb_node *node) -{ - return rbtree_simple_xattr_node_cmp(new_node, node) < 0; + return err ? err : size - remaining_size; } =20 /** @@ -1676,33 +1511,29 @@ static bool rbtree_simple_xattr_less(struct rb_node= *new_node, * Add an xattr object to @xattrs. This assumes no replacement or removal * of matching xattrs is wanted. Should only be called during inode * initialization when a few distinct initial xattrs are supposed to be se= t. + * + * Return: On success zero is returned. On failure a negative error code is + * returned. */ int simple_xattr_add(struct simple_xattrs *xattrs, struct simple_xattr *new_xattr) { - if (xattrs->use_rhashtable) - return rhashtable_insert_fast(&xattrs->ht, - &new_xattr->hash_node, - simple_xattr_params); - - write_lock(&xattrs->lock); - rb_add(&new_xattr->rb_node, &xattrs->rb_root, - rbtree_simple_xattr_less); - write_unlock(&xattrs->lock); - return 0; + return rhashtable_insert_fast(&xattrs->ht, &new_xattr->hash_node, + simple_xattr_params); } =20 /** * simple_xattrs_init - initialize new xattr header * @xattrs: header to initialize * - * Initialize relevant fields of a an xattr header. + * Initialize the rhashtable used to store xattr objects. + * + * Return: On success zero is returned. On failure a negative error code is + * returned. */ -void simple_xattrs_init(struct simple_xattrs *xattrs) +int simple_xattrs_init(struct simple_xattrs *xattrs) { - xattrs->use_rhashtable =3D false; - xattrs->rb_root =3D RB_ROOT; - rwlock_init(&xattrs->lock); + return rhashtable_init(&xattrs->ht, &simple_xattr_params); } =20 /** @@ -1710,7 +1541,8 @@ void simple_xattrs_init(struct simple_xattrs *xattrs) * * Dynamically allocate a simple_xattrs header and initialize the * underlying rhashtable. This is intended for consumers that want - * rhashtable-based xattr storage. + * to lazily allocate xattr storage only when the first xattr is set, + * avoiding the per-inode rhashtable overhead when no xattrs are used. * * Return: On success a new simple_xattrs is returned. On failure an * ERR_PTR is returned. @@ -1718,14 +1550,15 @@ void simple_xattrs_init(struct simple_xattrs *xattr= s) struct simple_xattrs *simple_xattrs_alloc(void) { struct simple_xattrs *xattrs __free(kfree) =3D NULL; + int ret; =20 xattrs =3D kzalloc(sizeof(*xattrs), GFP_KERNEL); if (!xattrs) return ERR_PTR(-ENOMEM); =20 - xattrs->use_rhashtable =3D true; - if (rhashtable_init(&xattrs->ht, &simple_xattr_params)) - return ERR_PTR(-ENOMEM); + ret =3D simple_xattrs_init(xattrs); + if (ret) + return ERR_PTR(ret); =20 return no_free_ptr(xattrs); } @@ -1784,28 +1617,10 @@ static void simple_xattr_ht_free(void *ptr, void *a= rg) */ void simple_xattrs_free(struct simple_xattrs *xattrs, size_t *freed_space) { + might_sleep(); + if (freed_space) *freed_space =3D 0; - - if (xattrs->use_rhashtable) { - rhashtable_free_and_destroy(&xattrs->ht, - simple_xattr_ht_free, freed_space); - } else { - struct rb_node *rbp; - - rbp =3D rb_first(&xattrs->rb_root); - while (rbp) { - struct simple_xattr *xattr; - struct rb_node *rbp_next; - - rbp_next =3D rb_next(rbp); - xattr =3D rb_entry(rbp, struct simple_xattr, rb_node); - rb_erase(&xattr->rb_node, &xattrs->rb_root); - if (freed_space) - *freed_space +=3D simple_xattr_space(xattr->name, - xattr->size); - simple_xattr_free(xattr); - rbp =3D rbp_next; - } - } + rhashtable_free_and_destroy(&xattrs->ht, simple_xattr_ht_free, + freed_space); } diff --git a/include/linux/xattr.h b/include/linux/xattr.h index 3063ecf0004d..f60357d9f938 100644 --- a/include/linux/xattr.h +++ b/include/linux/xattr.h @@ -107,18 +107,10 @@ static inline const char *xattr_prefix(const struct x= attr_handler *handler) } =20 struct simple_xattrs { - bool use_rhashtable; - union { - struct { - struct rb_root rb_root; - rwlock_t lock; - }; - struct rhashtable ht; - }; + struct rhashtable ht; }; =20 struct simple_xattr { - struct rb_node rb_node; struct rhash_head hash_node; struct rcu_head rcu; char *name; @@ -126,7 +118,7 @@ struct simple_xattr { char value[]; }; =20 -void simple_xattrs_init(struct simple_xattrs *xattrs); +int simple_xattrs_init(struct simple_xattrs *xattrs); struct simple_xattrs *simple_xattrs_alloc(void); struct simple_xattrs *simple_xattrs_lazy_alloc(struct simple_xattrs **xatt= rsp, const void *value, int flags); --=20 2.47.3 From nobody Thu Mar 19 02:04:58 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 963E431064B; Mon, 16 Feb 2026 13:32:44 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771248764; cv=none; b=L7txvH5DpR6H39b2tZ9cw83xBCFyKVtEGBX4zbvcf3rNgfya0g2KNKAuI7MdJpUQlqNzfQgdlMW1FLP9JXqf8gOmhEDF6e+y4nDyTgMX2iII7ESmIGHJDgBoftHsrvCjssbtAAH8Ke4J9dXPEqCY3bLbovuha8/RDrNwVUuIshw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771248764; c=relaxed/simple; bh=96SxHSPi59GBrRPEDM8gKWYEJ+q1qKqGM49g4nTkdB4=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=pvAI1ij/hWSp41rt+NHzXhmgnIkMkglMjUGtotEkQOj9Xwr/bUBeSW6q7MeV98UIWztN04IT3jBzsuu1LEG4O+w3+jAy2XDNmJcDlU346Wle+WWJd+OJ8xlLfd7CgDXJqUGJjfjeLeQWht7wSCZMecDiUAr/Y5MoWGjvxZeJKdY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=IKZnc2bF; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="IKZnc2bF" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 1BFF5C116C6; Mon, 16 Feb 2026 13:32:40 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1771248764; bh=96SxHSPi59GBrRPEDM8gKWYEJ+q1qKqGM49g4nTkdB4=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=IKZnc2bFfW0W82B1axiPsfeG/OH6odSnFk6t29PyOLxGZNrqeUK0mUj25LUOU7Ziv A3zbGKEOFHiWHaPfAokCeFQZSRrILdRzwlTIqUvTzIT66H6l0g8ywsCy9KPcUuLHFz jipGl3KpcuDtIWn1Pys7kE7xAoeXNKM95L3+1Qzt21agvqjmsgRFT9JK1az3ssdTT7 nDH5BQpoWz2BrV2zXLAJiN4JvemlPD3Vk+7+LOFvQEaHzXbWekHzZxk7D8a22pJwu+ Y83CBOWXA3QPiDwb9CBWSo2vt7UnEwVQja7kxsYT4mgSUwDYRi2WCVXoEHON8i6Ofr QY6gb/Tbu+rEA== From: Christian Brauner Date: Mon, 16 Feb 2026 14:32:03 +0100 Subject: [PATCH 07/14] xattr: add xattr_permission_error() Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260216-work-xattr-socket-v1-7-c2efa4f74cb7@kernel.org> References: <20260216-work-xattr-socket-v1-0-c2efa4f74cb7@kernel.org> In-Reply-To: <20260216-work-xattr-socket-v1-0-c2efa4f74cb7@kernel.org> To: linux-fsdevel@vger.kernel.org Cc: Jeff Layton , Josef Bacik , Alexander Viro , Jan Kara , linux-kernel@vger.kernel.org, Hugh Dickins , linux-mm@kvack.org, Greg Kroah-Hartman , Tejun Heo , Eric Dumazet , Jakub Kicinski , Jann Horn , netdev@vger.kernel.org, Christian Brauner X-Mailer: b4 0.15-dev-47773 X-Developer-Signature: v=1; a=openpgp-sha256; l=1499; i=brauner@kernel.org; h=from:subject:message-id; bh=96SxHSPi59GBrRPEDM8gKWYEJ+q1qKqGM49g4nTkdB4=; b=owGbwMvMwCU28Zj0gdSKO4sYT6slMWROlolTurXu/ddPcs+fdk/9U6Am8MhlAv83LwnD0mgpY 42qf+tdO0pZGMS4GGTFFFkc2k3C5ZbzVGw2ytSAmcPKBDKEgYtTACby35+RYZHzvxMMF7deDl7+ Luzuz1ger61N6y/bPDx6d2PX1idffi9l+Gd9eG9O78xDSiUs5hGruR/YiesulhGeujJ/7rPiMA6 u+fwA X-Developer-Key: i=brauner@kernel.org; a=openpgp; fpr=4880B8C9BD0E5106FC070F4F7B3C391EFEA93624 Stop repeating the ?: in multiple places and use a simple helper for this. Signed-off-by: Christian Brauner Acked-by: "Darrick J. Wong" Reviewed-by: Jan Kara Reviewed-by: Jeff Layton --- fs/xattr.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/fs/xattr.c b/fs/xattr.c index 64803097e1dc..c4db8663c32e 100644 --- a/fs/xattr.c +++ b/fs/xattr.c @@ -106,6 +106,13 @@ int may_write_xattr(struct mnt_idmap *idmap, struct in= ode *inode) return 0; } =20 +static inline int xattr_permission_error(int mask) +{ + if (mask & MAY_WRITE) + return -EPERM; + return -ENODATA; +} + /* * Check permissions for extended attribute access. This is a bit complic= ated * because different namespaces have very different rules. @@ -135,7 +142,7 @@ xattr_permission(struct mnt_idmap *idmap, struct inode = *inode, */ if (!strncmp(name, XATTR_TRUSTED_PREFIX, XATTR_TRUSTED_PREFIX_LEN)) { if (!capable(CAP_SYS_ADMIN)) - return (mask & MAY_WRITE) ? -EPERM : -ENODATA; + return xattr_permission_error(mask); return 0; } =20 @@ -146,7 +153,7 @@ xattr_permission(struct mnt_idmap *idmap, struct inode = *inode, */ if (!strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN)) { if (!S_ISREG(inode->i_mode) && !S_ISDIR(inode->i_mode)) - return (mask & MAY_WRITE) ? -EPERM : -ENODATA; + return xattr_permission_error(mask); if (S_ISDIR(inode->i_mode) && (inode->i_mode & S_ISVTX) && (mask & MAY_WRITE) && !inode_owner_or_capable(idmap, inode)) --=20 2.47.3 From nobody Thu Mar 19 02:04:58 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7752130F552; Mon, 16 Feb 2026 13:32:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771248768; cv=none; b=Je+3EYx7YvnrJXcEQVQAN0RAqmnNIYiY0m5Mk5bccB4mzoRIzqaxXhETzYct56Aygntu21zi8vHigwQ+r9F5XWOhr35K/7992zWNwfbY29W793AbpUZTjipv44SqKUWKARCpqpCvmwGV4aCRwbu0V5pzyxWnBCSDpH85zsRCh68= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771248768; c=relaxed/simple; bh=UGS1CMJ2ZqHjOV5UFVesoa2erJ7ZK4K3eqstwjrfN6M=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=a18LQeHCzbIkFOtBqBw4iTNrjtqvuIqiimg9D2kknN0JSNbjkGko4ZTf2OBMg7N614pBKivG+0x20A9N+3rGlus9dqBXDc3dUqOdr0J7EN/mbMR0bXwNTf+HeHmqbrtLqV1bKi353rggDHPKRz7ShOluJxaHnBeWb5fTsl0QLLo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=TiPhr6vn; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="TiPhr6vn" Received: by smtp.kernel.org (Postfix) with ESMTPSA id F26B7C19423; Mon, 16 Feb 2026 13:32:44 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1771248768; bh=UGS1CMJ2ZqHjOV5UFVesoa2erJ7ZK4K3eqstwjrfN6M=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=TiPhr6vn5MwWVSkOJv3YQl6S+YMB3GTyKrqaBvoIHCOVFVlId5zQ51AYLutQPHDNM hEE37rv7bajB/M8roxxMiv+JcR4Dv87prqre4Re+6uLLj4/gV7NVVYSOtNyxAJ2q2T mI1+iVb5sRdT6hybvU1q1a6FZK13ieKXJIa5c24VNOw8IRdM6t4sh3apUfmvVeWszK 9JpwsGvTrks93gn24gx05ZCSCoUPOvl6bDV4Ugip7ehjkdc2DDU+ta89hyBoBMiMsp V3M/6wdbAciIt6MDl9acNM1+IrEPlEh6ZAodpyeYNhNtd9+RvyG7bT3G1baMhS9wGe WtO5stfcFvORA== From: Christian Brauner Date: Mon, 16 Feb 2026 14:32:04 +0100 Subject: [PATCH 08/14] xattr: switch xattr_permission() to switch statement Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260216-work-xattr-socket-v1-8-c2efa4f74cb7@kernel.org> References: <20260216-work-xattr-socket-v1-0-c2efa4f74cb7@kernel.org> In-Reply-To: <20260216-work-xattr-socket-v1-0-c2efa4f74cb7@kernel.org> To: linux-fsdevel@vger.kernel.org Cc: Jeff Layton , Josef Bacik , Alexander Viro , Jan Kara , linux-kernel@vger.kernel.org, Hugh Dickins , linux-mm@kvack.org, Greg Kroah-Hartman , Tejun Heo , Eric Dumazet , Jakub Kicinski , Jann Horn , netdev@vger.kernel.org, Christian Brauner X-Mailer: b4 0.15-dev-47773 X-Developer-Signature: v=1; a=openpgp-sha256; l=1182; i=brauner@kernel.org; h=from:subject:message-id; bh=UGS1CMJ2ZqHjOV5UFVesoa2erJ7ZK4K3eqstwjrfN6M=; b=owGbwMvMwCU28Zj0gdSKO4sYT6slMWROlom7r3xTgsWCs13dmLdvkdeWCf9iTmy2fslg9iVwN tdipjcPOkpZGMS4GGTFFFkc2k3C5ZbzVGw2ytSAmcPKBDKEgYtTACYyo4uRoctEMG7jo3uVzvs8 3/HZrSnvMPGYJ3W90fNWyQG297WpZxn+V75jVduyUZ/vfIbm8ee8ckdDSgMmPxaYkR79/Zxx5dI JvAA= X-Developer-Key: i=brauner@kernel.org; a=openpgp; fpr=4880B8C9BD0E5106FC070F4F7B3C391EFEA93624 Simplify the codeflow by using a switch statement that switches on S_IFMT. Signed-off-by: Christian Brauner Acked-by: "Darrick J. Wong" Reviewed-by: Jan Kara Reviewed-by: Jeff Layton --- fs/xattr.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/fs/xattr.c b/fs/xattr.c index c4db8663c32e..328ed7558dfc 100644 --- a/fs/xattr.c +++ b/fs/xattr.c @@ -152,12 +152,20 @@ xattr_permission(struct mnt_idmap *idmap, struct inod= e *inode, * privileged users can write attributes. */ if (!strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN)) { - if (!S_ISREG(inode->i_mode) && !S_ISDIR(inode->i_mode)) - return xattr_permission_error(mask); - if (S_ISDIR(inode->i_mode) && (inode->i_mode & S_ISVTX) && - (mask & MAY_WRITE) && - !inode_owner_or_capable(idmap, inode)) + switch (inode->i_mode & S_IFMT) { + case S_IFREG: + break; + case S_IFDIR: + if (!(inode->i_mode & S_ISVTX)) + break; + if (!(mask & MAY_WRITE)) + break; + if (inode_owner_or_capable(idmap, inode)) + break; return -EPERM; + default: + return xattr_permission_error(mask); + } } =20 return inode_permission(idmap, inode, mask); --=20 2.47.3 From nobody Thu Mar 19 02:04:58 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id BDC6C311975; Mon, 16 Feb 2026 13:32:51 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771248771; cv=none; b=BG6TTj+nL9c+FS8v5m66UGDYLBQBAe/+RTXr1pXRTqbV+hZM64j3vvW73QjaD+wrgZxPxM1YjZE63shVWUViwY3Z98xGZbRvGtCmSx2DF8w1Fe+RuyuHLQEpxIlduhRyJLGGaIZkKaWcbuBF76BnTtVhdIMvKHUInjU3cyHYd+Q= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771248771; c=relaxed/simple; bh=JWJ0LlOygERvwjt59MZEWYplPXeoWPP4MOafVL9EkMk=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=UcnSSL426GFSK5PoZ0J/vBvH6XsO0Cc9qzNlKX4r9Ltg+JUkgja3Nvau0j38QFZWmIV0VKukzmGr04//Zy0cLyIfINLMDnJZwipEtRBmuDVfttzVKh9Tm8d+TBW6LPQFbK002gXBmcxSnQEeJhaul2mnshAH1VLaNb+/ILXcPDM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=V62U8/Qj; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="V62U8/Qj" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 94854C19424; Mon, 16 Feb 2026 13:32:48 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1771248771; bh=JWJ0LlOygERvwjt59MZEWYplPXeoWPP4MOafVL9EkMk=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=V62U8/QjirJ13wSb8V/oo5CuvZaDH10OxO/5NKTeBt8+ZgOx20ElbtNIMbkAYJvjn rMuuc1cB7NxEEk15DoZ2yphohhqy9IDgs/3We3S/wYXe1JzFZaA/i0wmFfrhfjqSJ9 IBhW16W+de+jyBIraagX92JCVMklOXJtu/UPSBUjjGdHfh410Z3qjOuhMg7rPHP8In bJMdhSvBBoztlVWVwKt03VIT8OwFCpyAGwcrGyhZTU4V5OqTYsHzqe2rgxdTacA4CX O/Cdowq/Rlpr331x1Q70nz1KiWVKPoWKNnF+rasICFNc5CDBdBdaYglUNVcLCf2NUJ mCFAt2/n2CE8w== From: Christian Brauner Date: Mon, 16 Feb 2026 14:32:05 +0100 Subject: [PATCH 09/14] xattr: move user limits for xattrs to generic infra Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260216-work-xattr-socket-v1-9-c2efa4f74cb7@kernel.org> References: <20260216-work-xattr-socket-v1-0-c2efa4f74cb7@kernel.org> In-Reply-To: <20260216-work-xattr-socket-v1-0-c2efa4f74cb7@kernel.org> To: linux-fsdevel@vger.kernel.org Cc: Jeff Layton , Josef Bacik , Alexander Viro , Jan Kara , linux-kernel@vger.kernel.org, Hugh Dickins , linux-mm@kvack.org, Greg Kroah-Hartman , Tejun Heo , Eric Dumazet , Jakub Kicinski , Jann Horn , netdev@vger.kernel.org, Christian Brauner X-Mailer: b4 0.15-dev-47773 X-Developer-Signature: v=1; a=openpgp-sha256; l=8059; i=brauner@kernel.org; h=from:subject:message-id; bh=JWJ0LlOygERvwjt59MZEWYplPXeoWPP4MOafVL9EkMk=; b=owGbwMvMwCU28Zj0gdSKO4sYT6slMWROlonTOp3LVHmeXSq9ZH9L4dZlfY+/PDvh8jdoxQxmQ VWXVbN2dJSyMIhxMciKKbI4tJuEyy3nqdhslKkBM4eVCWQIAxenAEzE2IaR4VdxSfXvMw+a81ma FLO7H8hxC0/uXFu5v3T1ntIJV7L7zRgZ9v9KE4phuq96yljdndEmj8/jm13Pl7qq3+3f9hwsq1n OBQA= X-Developer-Key: i=brauner@kernel.org; a=openpgp; fpr=4880B8C9BD0E5106FC070F4F7B3C391EFEA93624 Signed-off-by: Christian Brauner Acked-by: "Darrick J. Wong" Reviewed-by: Jan Kara Reviewed-by: Jeff Layton --- fs/kernfs/inode.c | 75 ++---------------------------------------= ---- fs/kernfs/kernfs-internal.h | 3 +- fs/xattr.c | 65 +++++++++++++++++++++++++++++++++++++++ include/linux/kernfs.h | 2 -- include/linux/xattr.h | 18 +++++++++++ 5 files changed, 87 insertions(+), 76 deletions(-) diff --git a/fs/kernfs/inode.c b/fs/kernfs/inode.c index dfc3315b5afc..1de10500842d 100644 --- a/fs/kernfs/inode.c +++ b/fs/kernfs/inode.c @@ -45,8 +45,7 @@ static struct kernfs_iattrs *__kernfs_iattrs(struct kernf= s_node *kn, bool alloc) ret->ia_mtime =3D ret->ia_atime; ret->ia_ctime =3D ret->ia_atime; =20 - atomic_set(&ret->nr_user_xattrs, 0); - atomic_set(&ret->user_xattr_size, 0); + simple_xattr_limits_init(&ret->xattr_limits); =20 /* If someone raced us, recognize it. */ if (!try_cmpxchg(&kn->iattr, &attr, ret)) @@ -355,69 +354,6 @@ static int kernfs_vfs_xattr_set(const struct xattr_han= dler *handler, return kernfs_xattr_set(kn, name, value, size, flags); } =20 -static int kernfs_vfs_user_xattr_add(struct kernfs_node *kn, - const char *full_name, - struct simple_xattrs *xattrs, - const void *value, size_t size, int flags) -{ - struct kernfs_iattrs *attr =3D kernfs_iattrs_noalloc(kn); - atomic_t *sz =3D &attr->user_xattr_size; - atomic_t *nr =3D &attr->nr_user_xattrs; - struct simple_xattr *old_xattr; - int ret; - - if (atomic_inc_return(nr) > KERNFS_MAX_USER_XATTRS) { - ret =3D -ENOSPC; - goto dec_count_out; - } - - if (atomic_add_return(size, sz) > KERNFS_USER_XATTR_SIZE_LIMIT) { - ret =3D -ENOSPC; - goto dec_size_out; - } - - old_xattr =3D simple_xattr_set(xattrs, full_name, value, size, flags); - if (!old_xattr) - return 0; - - if (IS_ERR(old_xattr)) { - ret =3D PTR_ERR(old_xattr); - goto dec_size_out; - } - - ret =3D 0; - size =3D old_xattr->size; - simple_xattr_free_rcu(old_xattr); -dec_size_out: - atomic_sub(size, sz); -dec_count_out: - atomic_dec(nr); - return ret; -} - -static int kernfs_vfs_user_xattr_rm(struct kernfs_node *kn, - const char *full_name, - struct simple_xattrs *xattrs, - const void *value, size_t size, int flags) -{ - struct kernfs_iattrs *attr =3D kernfs_iattrs_noalloc(kn); - atomic_t *sz =3D &attr->user_xattr_size; - atomic_t *nr =3D &attr->nr_user_xattrs; - struct simple_xattr *old_xattr; - - old_xattr =3D simple_xattr_set(xattrs, full_name, value, size, flags); - if (!old_xattr) - return 0; - - if (IS_ERR(old_xattr)) - return PTR_ERR(old_xattr); - - atomic_sub(old_xattr->size, sz); - atomic_dec(nr); - simple_xattr_free_rcu(old_xattr); - return 0; -} - static int kernfs_vfs_user_xattr_set(const struct xattr_handler *handler, struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, @@ -440,13 +376,8 @@ static int kernfs_vfs_user_xattr_set(const struct xatt= r_handler *handler, if (IS_ERR_OR_NULL(xattrs)) return PTR_ERR(xattrs); =20 - if (value) - return kernfs_vfs_user_xattr_add(kn, full_name, xattrs, - value, size, flags); - else - return kernfs_vfs_user_xattr_rm(kn, full_name, xattrs, - value, size, flags); - + return simple_xattr_set_limited(xattrs, &attrs->xattr_limits, + full_name, value, size, flags); } =20 static const struct xattr_handler kernfs_trusted_xattr_handler =3D { diff --git a/fs/kernfs/kernfs-internal.h b/fs/kernfs/kernfs-internal.h index 1324ed8c0661..1d3831e3a270 100644 --- a/fs/kernfs/kernfs-internal.h +++ b/fs/kernfs/kernfs-internal.h @@ -27,8 +27,7 @@ struct kernfs_iattrs { struct timespec64 ia_ctime; =20 struct simple_xattrs *xattrs; - atomic_t nr_user_xattrs; - atomic_t user_xattr_size; + struct simple_xattr_limits xattr_limits; }; =20 struct kernfs_root { diff --git a/fs/xattr.c b/fs/xattr.c index 328ed7558dfc..5e559b1c651f 100644 --- a/fs/xattr.c +++ b/fs/xattr.c @@ -1427,6 +1427,71 @@ struct simple_xattr *simple_xattr_set(struct simple_= xattrs *xattrs, return old_xattr; } =20 +static inline void simple_xattr_limits_dec(struct simple_xattr_limits *lim= its, + size_t size) +{ + atomic_sub(size, &limits->xattr_size); + atomic_dec(&limits->nr_xattrs); +} + +static inline int simple_xattr_limits_inc(struct simple_xattr_limits *limi= ts, + size_t size) +{ + if (atomic_inc_return(&limits->nr_xattrs) > SIMPLE_XATTR_MAX_NR) { + atomic_dec(&limits->nr_xattrs); + return -ENOSPC; + } + + if (atomic_add_return(size, &limits->xattr_size) <=3D SIMPLE_XATTR_MAX_SI= ZE) + return 0; + + simple_xattr_limits_dec(limits, size); + return -ENOSPC; +} + +/** + * simple_xattr_set_limited - set an xattr with per-inode user.* limits + * @xattrs: the header of the xattr object + * @limits: per-inode limit counters for user.* xattrs + * @name: the name of the xattr to set or remove + * @value: the value to store (NULL to remove) + * @size: the size of @value + * @flags: XATTR_CREATE, XATTR_REPLACE, or 0 + * + * Like simple_xattr_set(), but enforces per-inode count and total value s= ize + * limits for user.* xattrs. Uses speculative pre-increment of the atomic + * counters to avoid races without requiring external locks. + * + * Return: On success zero is returned. On failure a negative error code is + * returned. + */ +int simple_xattr_set_limited(struct simple_xattrs *xattrs, + struct simple_xattr_limits *limits, + const char *name, const void *value, + size_t size, int flags) +{ + struct simple_xattr *old_xattr; + int ret; + + if (value) { + ret =3D simple_xattr_limits_inc(limits, size); + if (ret) + return ret; + } + + old_xattr =3D simple_xattr_set(xattrs, name, value, size, flags); + if (IS_ERR(old_xattr)) { + if (value) + simple_xattr_limits_dec(limits, size); + return PTR_ERR(old_xattr); + } + if (old_xattr) { + simple_xattr_limits_dec(limits, old_xattr->size); + simple_xattr_free_rcu(old_xattr); + } + return 0; +} + static bool xattr_is_trusted(const char *name) { return !strncmp(name, XATTR_TRUSTED_PREFIX, XATTR_TRUSTED_PREFIX_LEN); diff --git a/include/linux/kernfs.h b/include/linux/kernfs.h index b5a5f32fdfd1..d8f57f0af5e4 100644 --- a/include/linux/kernfs.h +++ b/include/linux/kernfs.h @@ -99,8 +99,6 @@ enum kernfs_node_type { =20 #define KERNFS_TYPE_MASK 0x000f #define KERNFS_FLAG_MASK ~KERNFS_TYPE_MASK -#define KERNFS_MAX_USER_XATTRS 128 -#define KERNFS_USER_XATTR_SIZE_LIMIT (128 << 10) =20 enum kernfs_node_flag { KERNFS_ACTIVATED =3D 0x0010, diff --git a/include/linux/xattr.h b/include/linux/xattr.h index f60357d9f938..90a43a117127 100644 --- a/include/linux/xattr.h +++ b/include/linux/xattr.h @@ -118,6 +118,20 @@ struct simple_xattr { char value[]; }; =20 +#define SIMPLE_XATTR_MAX_NR 128 +#define SIMPLE_XATTR_MAX_SIZE (128 << 10) + +struct simple_xattr_limits { + atomic_t nr_xattrs; /* current user.* xattr count */ + atomic_t xattr_size; /* current total user.* value bytes */ +}; + +static inline void simple_xattr_limits_init(struct simple_xattr_limits *li= mits) +{ + atomic_set(&limits->nr_xattrs, 0); + atomic_set(&limits->xattr_size, 0); +} + int simple_xattrs_init(struct simple_xattrs *xattrs); struct simple_xattrs *simple_xattrs_alloc(void); struct simple_xattrs *simple_xattrs_lazy_alloc(struct simple_xattrs **xatt= rsp, @@ -132,6 +146,10 @@ int simple_xattr_get(struct simple_xattrs *xattrs, con= st char *name, struct simple_xattr *simple_xattr_set(struct simple_xattrs *xattrs, const char *name, const void *value, size_t size, int flags); +int simple_xattr_set_limited(struct simple_xattrs *xattrs, + struct simple_xattr_limits *limits, + const char *name, const void *value, + size_t size, int flags); ssize_t simple_xattr_list(struct inode *inode, struct simple_xattrs *xattr= s, char *buffer, size_t size); int simple_xattr_add(struct simple_xattrs *xattrs, --=20 2.47.3 From nobody Thu Mar 19 02:04:58 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CE6F030F934; Mon, 16 Feb 2026 13:32:55 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771248775; cv=none; b=eQVuKAJEq70pXr4odPFOw43ITkHGW57e9BoAnE0QJXn2ie7+qrIyyDNGdjZD0lXftWQxCz7BnIEQnNdwM7OAj3YRHqZSySgEBdQMjdMZSd5TKlfxPzVPC7O/VD0l3hVIdJPIw21eqUf/a3YPCXbddOtS8YgrZOY0WFCVJXf/ErU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771248775; c=relaxed/simple; bh=og9OzjKrpi8J5E7G7hJ+vPxRQ2I1HIq12Wata8xWsm0=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=IasE0YcJcFTR3aHQzHRCb3WhHyT738+Jz3JAL0fVHYC4vn4Rnaies6Lr6q8Z+EdYP23Izrn23dZWYkhSGH0a8RciamDCYqvm+PNRCgQTlGqgIZLbRfEoqq87KxWspTuMdBdV0i8To63Bx/uhCDG5M10zAtdPnpKceKmVMsG8jcc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=D7ZNZED1; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="D7ZNZED1" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 169F6C116C6; Mon, 16 Feb 2026 13:32:51 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1771248775; bh=og9OzjKrpi8J5E7G7hJ+vPxRQ2I1HIq12Wata8xWsm0=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=D7ZNZED1zVSUgXHnknRdsN5fnqrXmOkp67JAmSwDSe+PK8bQFgt96/kDlQo6vPy+e BtM+EBnrBA2O/wQT8jZWiwz0THc1m2jdshzViEFjOlNKWXKETkMPfJYUVAY/oKuMi/ YRcDj4uqfAisaXLvE2ZqQlmwx2Fl+UuQVZDRR3Q4A+zR0QqncNqtkl1HIUTW8eId3W yG9hHZEZmservuk4/LjGzCfDRM3RUAxC0jqiXoa9nmmHcS4DFx3hpqMZNDPEvzwBwG Xa3Y+eqrTklaWmhpza74vXKPcQ5ZuOpqpn8xVQ2kSwWVwU70MpzjPsF/vmJ982xeN9 Kg/3z4Ayz35Xg== From: Christian Brauner Date: Mon, 16 Feb 2026 14:32:06 +0100 Subject: [PATCH 10/14] xattr,net: support limited amount of extended attributes on sockfs sockets Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260216-work-xattr-socket-v1-10-c2efa4f74cb7@kernel.org> References: <20260216-work-xattr-socket-v1-0-c2efa4f74cb7@kernel.org> In-Reply-To: <20260216-work-xattr-socket-v1-0-c2efa4f74cb7@kernel.org> To: linux-fsdevel@vger.kernel.org Cc: Jeff Layton , Josef Bacik , Alexander Viro , Jan Kara , linux-kernel@vger.kernel.org, Hugh Dickins , linux-mm@kvack.org, Greg Kroah-Hartman , Tejun Heo , Eric Dumazet , Jakub Kicinski , Jann Horn , netdev@vger.kernel.org, Christian Brauner X-Mailer: b4 0.15-dev-47773 X-Developer-Signature: v=1; a=openpgp-sha256; l=5847; i=brauner@kernel.org; h=from:subject:message-id; bh=og9OzjKrpi8J5E7G7hJ+vPxRQ2I1HIq12Wata8xWsm0=; b=owGbwMvMwCU28Zj0gdSKO4sYT6slMWROlomz2jXPdLuwPOdjplbdwIYlMzUXVL7iWrP81ndR0 7p3z7RcOkpZGMS4GGTFFFkc2k3C5ZbzVGw2ytSAmcPKBDKEgYtTACYyfyMjw+4fXYaf2bsnPbg7 c/LP93saJ1y3nx97u7Of58fyldsl/AUZ/tnnXFZP07TV1dp5duaDLW6FUl8e2m9x/W+8nduoa31 nBB8A X-Developer-Key: i=brauner@kernel.org; a=openpgp; fpr=4880B8C9BD0E5106FC070F4F7B3C391EFEA93624 Now that we've generalized the infrastructure for user.* xattrs make it possible to set up to 128 user.* extended attributes on a sockfs inode or up to 128kib. kernfs (cgroupfs) has the same limits and it has proven to be quite sufficient for nearly all use-cases. This will allow containers to label sockets and will e.g., be used by systemd and Gnome to find various sockets in containers where high-privilege or more complicated solutions aren't available. Signed-off-by: Christian Brauner Acked-by: "Darrick J. Wong" Reviewed-by: Jan Kara Reviewed-by: Jeff Layton --- net/socket.c | 119 +++++++++++++++++++++++++++++++++++++++++++++----------= ---- 1 file changed, 92 insertions(+), 27 deletions(-) diff --git a/net/socket.c b/net/socket.c index 136b98c54fb3..7aa94fce7a8b 100644 --- a/net/socket.c +++ b/net/socket.c @@ -315,45 +315,70 @@ static int move_addr_to_user(struct sockaddr_storage = *kaddr, int klen, =20 static struct kmem_cache *sock_inode_cachep __ro_after_init; =20 +struct sockfs_inode { + struct simple_xattrs *xattrs; + struct simple_xattr_limits xattr_limits; + struct socket_alloc; +}; + +static struct sockfs_inode *SOCKFS_I(struct inode *inode) +{ + return container_of(inode, struct sockfs_inode, vfs_inode); +} + static struct inode *sock_alloc_inode(struct super_block *sb) { - struct socket_alloc *ei; + struct sockfs_inode *si; =20 - ei =3D alloc_inode_sb(sb, sock_inode_cachep, GFP_KERNEL); - if (!ei) + si =3D alloc_inode_sb(sb, sock_inode_cachep, GFP_KERNEL); + if (!si) return NULL; - init_waitqueue_head(&ei->socket.wq.wait); - ei->socket.wq.fasync_list =3D NULL; - ei->socket.wq.flags =3D 0; + si->xattrs =3D NULL; + simple_xattr_limits_init(&si->xattr_limits); + + init_waitqueue_head(&si->socket.wq.wait); + si->socket.wq.fasync_list =3D NULL; + si->socket.wq.flags =3D 0; + + si->socket.state =3D SS_UNCONNECTED; + si->socket.flags =3D 0; + si->socket.ops =3D NULL; + si->socket.sk =3D NULL; + si->socket.file =3D NULL; =20 - ei->socket.state =3D SS_UNCONNECTED; - ei->socket.flags =3D 0; - ei->socket.ops =3D NULL; - ei->socket.sk =3D NULL; - ei->socket.file =3D NULL; + return &si->vfs_inode; +} + +static void sock_evict_inode(struct inode *inode) +{ + struct sockfs_inode *si =3D SOCKFS_I(inode); + struct simple_xattrs *xattrs =3D si->xattrs; =20 - return &ei->vfs_inode; + if (xattrs) { + simple_xattrs_free(xattrs, NULL); + kfree(xattrs); + } + clear_inode(inode); } =20 static void sock_free_inode(struct inode *inode) { - struct socket_alloc *ei; + struct sockfs_inode *si =3D SOCKFS_I(inode); =20 - ei =3D container_of(inode, struct socket_alloc, vfs_inode); - kmem_cache_free(sock_inode_cachep, ei); + kmem_cache_free(sock_inode_cachep, si); } =20 static void init_once(void *foo) { - struct socket_alloc *ei =3D (struct socket_alloc *)foo; + struct sockfs_inode *si =3D (struct sockfs_inode *)foo; =20 - inode_init_once(&ei->vfs_inode); + inode_init_once(&si->vfs_inode); } =20 static void init_inodecache(void) { sock_inode_cachep =3D kmem_cache_create("sock_inode_cache", - sizeof(struct socket_alloc), + sizeof(struct sockfs_inode), 0, (SLAB_HWCACHE_ALIGN | SLAB_RECLAIM_ACCOUNT | @@ -365,6 +390,7 @@ static void init_inodecache(void) static const struct super_operations sockfs_ops =3D { .alloc_inode =3D sock_alloc_inode, .free_inode =3D sock_free_inode, + .evict_inode =3D sock_evict_inode, .statfs =3D simple_statfs, }; =20 @@ -417,9 +443,48 @@ static const struct xattr_handler sockfs_security_xatt= r_handler =3D { .set =3D sockfs_security_xattr_set, }; =20 +static int sockfs_user_xattr_get(const struct xattr_handler *handler, + struct dentry *dentry, struct inode *inode, + const char *suffix, void *value, size_t size) +{ + const char *name =3D xattr_full_name(handler, suffix); + struct simple_xattrs *xattrs; + + xattrs =3D READ_ONCE(SOCKFS_I(inode)->xattrs); + if (!xattrs) + return -ENODATA; + + return simple_xattr_get(xattrs, name, value, size); +} + +static int sockfs_user_xattr_set(const struct xattr_handler *handler, + struct mnt_idmap *idmap, + struct dentry *dentry, struct inode *inode, + const char *suffix, const void *value, + size_t size, int flags) +{ + const char *name =3D xattr_full_name(handler, suffix); + struct sockfs_inode *si =3D SOCKFS_I(inode); + struct simple_xattrs *xattrs; + + xattrs =3D simple_xattrs_lazy_alloc(&si->xattrs, value, flags); + if (IS_ERR_OR_NULL(xattrs)) + return PTR_ERR(xattrs); + + return simple_xattr_set_limited(xattrs, &si->xattr_limits, + name, value, size, flags); +} + +static const struct xattr_handler sockfs_user_xattr_handler =3D { + .prefix =3D XATTR_USER_PREFIX, + .get =3D sockfs_user_xattr_get, + .set =3D sockfs_user_xattr_set, +}; + static const struct xattr_handler * const sockfs_xattr_handlers[] =3D { &sockfs_xattr_handler, &sockfs_security_xattr_handler, + &sockfs_user_xattr_handler, NULL }; =20 @@ -572,26 +637,26 @@ EXPORT_SYMBOL(sockfd_lookup); static ssize_t sockfs_listxattr(struct dentry *dentry, char *buffer, size_t size) { - ssize_t len; - ssize_t used =3D 0; + struct sockfs_inode *si =3D SOCKFS_I(d_inode(dentry)); + ssize_t len, used; =20 - len =3D security_inode_listsecurity(d_inode(dentry), buffer, size); + len =3D simple_xattr_list(d_inode(dentry), READ_ONCE(si->xattrs), + buffer, size); if (len < 0) return len; - used +=3D len; + + used =3D len; if (buffer) { - if (size < used) - return -ERANGE; buffer +=3D len; + size -=3D len; } =20 - len =3D (XATTR_NAME_SOCKPROTONAME_LEN + 1); + len =3D XATTR_NAME_SOCKPROTONAME_LEN + 1; used +=3D len; if (buffer) { - if (size < used) + if (size < len) return -ERANGE; memcpy(buffer, XATTR_NAME_SOCKPROTONAME, len); - buffer +=3D len; } =20 return used; --=20 2.47.3 From nobody Thu Mar 19 02:04:58 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 92DBD30E857; Mon, 16 Feb 2026 13:32:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771248779; cv=none; b=sF2TIrhTgFxHjCxpN+WGdRMwSrk/xkPkmEemY7v2HnQu2bi43+lhPiD1UrPTwXd8KJkmuovT4gyzfL61NfIzYyJ0phAQmK2/m7BQosBmF4+4rWEhEKL3zKwwdWZkzAye3ICZsi2BV0Xxy2FmInqQ3u8Avl3/p42aEVY1bqInJTg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771248779; c=relaxed/simple; bh=RnCZFxNnhxA177tCuXFiB0/3HD/vnvQpBZpzUbgVZdc=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=WHZGSjaNArb7SvTksRfB7Z/VR/GSdABOjG/94mP6N2E809SvRGP6tT7FHDzeD0p6GWE9ZjA1ie823ETIMbhVSdP9/Xajv4NBPce62ZmCbG32Po470g8k2YUiiLjKOzG9NWB/T+5Ry4xpS1UZbxPx6Lwc/l2P6zLXx8+UEC5xqAU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=O5LM061Q; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="O5LM061Q" Received: by smtp.kernel.org (Postfix) with ESMTPSA id E7FC7C19423; Mon, 16 Feb 2026 13:32:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1771248779; bh=RnCZFxNnhxA177tCuXFiB0/3HD/vnvQpBZpzUbgVZdc=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=O5LM061Qtdc+RkTJ4/N5R8kAr7hzZ7s50jqzOBi+fEg+kC3HEm/xE24/W1+q4z3pZ l03F5nAjt3apRdw2Ksa5uYkWM9RlxrZELHlHfDvaR2dmdyieuF1gYEA+CLzHG3K5Zo hveIyw8RD/3Rw11fvDlzLVH7svCb4ztZUmwvqM5vkSQy7GKP3rUm2pbNk9PGNRbne6 0fd1Of8J6e+ko29M9dK03PakG/AyspXn8i5AyNlU0q5AwVW4IRTwdnwYBU8AVwbTHL naj6c4Bp4Vqree6WggIG5GPCCMGgeKoKzUQroxtjHX3hQr3qLjqDDj+5AvdB9eEDT9 O9k6J8KtHhRzA== From: Christian Brauner Date: Mon, 16 Feb 2026 14:32:07 +0100 Subject: [PATCH 11/14] xattr: support extended attributes on sockets Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260216-work-xattr-socket-v1-11-c2efa4f74cb7@kernel.org> References: <20260216-work-xattr-socket-v1-0-c2efa4f74cb7@kernel.org> In-Reply-To: <20260216-work-xattr-socket-v1-0-c2efa4f74cb7@kernel.org> To: linux-fsdevel@vger.kernel.org Cc: Jeff Layton , Josef Bacik , Alexander Viro , Jan Kara , linux-kernel@vger.kernel.org, Hugh Dickins , linux-mm@kvack.org, Greg Kroah-Hartman , Tejun Heo , Eric Dumazet , Jakub Kicinski , Jann Horn , netdev@vger.kernel.org, Christian Brauner X-Mailer: b4 0.15-dev-47773 X-Developer-Signature: v=1; a=openpgp-sha256; l=1672; i=brauner@kernel.org; h=from:subject:message-id; bh=RnCZFxNnhxA177tCuXFiB0/3HD/vnvQpBZpzUbgVZdc=; b=owGbwMvMwCU28Zj0gdSKO4sYT6slMWROlom75pWRtWaXcMwRHq8H2a+DPy2JfKutp7ynLSYjv mCC2uT7HaUsDGJcDLJiiiwO7Sbhcst5KjYbZWrAzGFlAhnCwMUpABPJ+cbwP5+Hi/3Amr7r9hsn bJbv/VPY9fvWH8bTcg+465bnyf6wTWRkuKLrFzW1XyvgQgqbrm5TX2Pi5L1dpevzl3Skn2+YypD CCwA= X-Developer-Key: i=brauner@kernel.org; a=openpgp; fpr=4880B8C9BD0E5106FC070F4F7B3C391EFEA93624 Allow user.* extended attributes on sockets by adding S_IFSOCK to the xattr_permission() switch statement. Previously user.* xattrs were only permitted on regular files and directories. Symlinks and special files including sockets were rejected with -EPERM. Path-based AF_UNIX sockets have their inodes on the underlying filesystem (e.g. tmpfs) which already supports user.* xattrs through simple_xattrs. So for these the permission check was the only thing missing. For sockets in sockfs - everything created via socket() including abstract namespace AF_UNIX sockets - the preceding patch added simple_xattr storage with per-inode limits. With the permission check lifted here these sockets can now store user.* xattrs as well. This enables services to associate metadata with their sockets. For example, a service using Varlink for IPC can label its socket with user.varlink=3D1 allowing eBPF programs to selectively capture traffic and tools to discover IPC entrypoints by enumerating bound sockets via netlink. Similarly, protocol negotiation can be performed through xattrs such as indicating RFC 5424 structured syslog support on /dev/log. Signed-off-by: Christian Brauner Acked-by: "Darrick J. Wong" Reviewed-by: Jan Kara Reviewed-by: Jeff Layton --- fs/xattr.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/xattr.c b/fs/xattr.c index 5e559b1c651f..09ecbaaa1660 100644 --- a/fs/xattr.c +++ b/fs/xattr.c @@ -163,6 +163,8 @@ xattr_permission(struct mnt_idmap *idmap, struct inode = *inode, if (inode_owner_or_capable(idmap, inode)) break; return -EPERM; + case S_IFSOCK: + break; default: return xattr_permission_error(mask); } --=20 2.47.3 From nobody Thu Mar 19 02:04:58 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id EA85930DD0C; Mon, 16 Feb 2026 13:33:02 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771248783; cv=none; b=mWGBBt2ceHAh54R34MBJpSFEKsQUjDiU7rsCA/uewbhywGhOeZuEuoRX+7QRCeD/Pq3VB1yXf7ydFtnjyXkTkKHp4b50awv0FTHRFGm+4mSxbzkvouF7c+GTndPB/SQhKDHfnWblJvM3NLgj+jxwlMQ8K/XYHODW4Q74gK3WW9I= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771248783; c=relaxed/simple; bh=attc5XwzII0O55hshNdwieE9+UGv31LLE3x0ZMxZ0sg=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=au2u3kN8ESfYZzt69wOC2rCvxJW4z+GgHvSnU/m2Zc9ZmVuKGmz54eIIMcn/m4w+CLr4avZx/qraIj2QiXHSsObeTd32hgD/CCedH4o86ckdmnzYuIKdQB93p0zbfsuH0ufTVHV6oxbjwU83674DCpGoMjZDLs2qrF6128S7HtE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=YBTOjDn2; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="YBTOjDn2" Received: by smtp.kernel.org (Postfix) with ESMTPSA id A2665C19424; Mon, 16 Feb 2026 13:32:59 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1771248782; bh=attc5XwzII0O55hshNdwieE9+UGv31LLE3x0ZMxZ0sg=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=YBTOjDn21sBBW/YsxL8mwueUP2kHkVGWc4DCx9NVGJ8F7EZiCr+1Bfl+ElOJNhZjp j7vRSgIbZc4/CMAR+kST4GNnLNHVlQ/dY+4l1LbQDWFBogKhX7IhvgjyVc47o8vEDL WSxmIPn0TA+bqg3zXOyWt4kwO5asjKt+O/BbykvpE3vybbxThDH0pciMmD7KnwMsws 3iqSzXVZnqskyWFs2RzNPxjUWCkkRGZ7kb8BNrPqMDKUmrWbNR6c2BnTBxps5lgvTV kCPy7MBtiIU9j11HUmxlPNu81l+xB/2vTxP35VDFU6DfH016/Rgl3G3CyU0Md5ZN5z aXEb3aZhhWeGQ== From: Christian Brauner Date: Mon, 16 Feb 2026 14:32:08 +0100 Subject: [PATCH 12/14] selftests/xattr: path-based AF_UNIX socket xattr tests Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260216-work-xattr-socket-v1-12-c2efa4f74cb7@kernel.org> References: <20260216-work-xattr-socket-v1-0-c2efa4f74cb7@kernel.org> In-Reply-To: <20260216-work-xattr-socket-v1-0-c2efa4f74cb7@kernel.org> To: linux-fsdevel@vger.kernel.org Cc: Jeff Layton , Josef Bacik , Alexander Viro , Jan Kara , linux-kernel@vger.kernel.org, Hugh Dickins , linux-mm@kvack.org, Greg Kroah-Hartman , Tejun Heo , Eric Dumazet , Jakub Kicinski , Jann Horn , netdev@vger.kernel.org, Christian Brauner X-Mailer: b4 0.15-dev-47773 X-Developer-Signature: v=1; a=openpgp-sha256; l=14115; i=brauner@kernel.org; h=from:subject:message-id; bh=attc5XwzII0O55hshNdwieE9+UGv31LLE3x0ZMxZ0sg=; b=owGbwMvMwCU28Zj0gdSKO4sYT6slMWROlolbPeHC9KnFa1/Ktj9/ZHsl6qOFcuR+y9nrCiL4e 70mnT7d1lHKwiDGxSArpsji0G4SLrecp2KzUaYGzBxWJpAhDFycAjCRNVkM/4Pr43eFLesvNPax EZrzcmJyVP9FjlBvpS2mmx6lzzHW/8nwv0z3JNOijkdqd2/NOy2vGut8jE/vz3Oe58u2fZsiK/j OiR8A X-Developer-Key: i=brauner@kernel.org; a=openpgp; fpr=4880B8C9BD0E5106FC070F4F7B3C391EFEA93624 Test user.* extended attribute operations on path-based Unix domain sockets (SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET). Path-based sockets are bound to a filesystem path and their inodes live on the underlying filesystem (e.g. tmpfs). Covers set/get/list/remove, persistence, XATTR_CREATE/XATTR_REPLACE flags, empty values, size queries, buffer-too-small errors, O_PATH fd operations, and trusted.* xattr handling. Signed-off-by: Christian Brauner Acked-by: "Darrick J. Wong" Reviewed-by: Jan Kara Reviewed-by: Jeff Layton --- .../testing/selftests/filesystems/xattr/.gitignore | 1 + tools/testing/selftests/filesystems/xattr/Makefile | 6 + .../filesystems/xattr/xattr_socket_test.c | 470 +++++++++++++++++= ++++ 3 files changed, 477 insertions(+) diff --git a/tools/testing/selftests/filesystems/xattr/.gitignore b/tools/t= esting/selftests/filesystems/xattr/.gitignore new file mode 100644 index 000000000000..5fd015d2257a --- /dev/null +++ b/tools/testing/selftests/filesystems/xattr/.gitignore @@ -0,0 +1 @@ +xattr_socket_test diff --git a/tools/testing/selftests/filesystems/xattr/Makefile b/tools/tes= ting/selftests/filesystems/xattr/Makefile new file mode 100644 index 000000000000..e3d8dca80faa --- /dev/null +++ b/tools/testing/selftests/filesystems/xattr/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 + +CFLAGS +=3D $(KHDR_INCLUDES) +TEST_GEN_PROGS :=3D xattr_socket_test + +include ../../lib.mk diff --git a/tools/testing/selftests/filesystems/xattr/xattr_socket_test.c = b/tools/testing/selftests/filesystems/xattr/xattr_socket_test.c new file mode 100644 index 000000000000..fac0a4c6bc05 --- /dev/null +++ b/tools/testing/selftests/filesystems/xattr/xattr_socket_test.c @@ -0,0 +1,470 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2026 Christian Brauner +/* + * Test extended attributes on path-based Unix domain sockets. + * + * Path-based Unix domain sockets are bound to a filesystem path and their + * inodes live on the underlying filesystem (e.g. tmpfs). These tests veri= fy + * that user.* and trusted.* xattr operations work correctly on them using + * path-based syscalls (setxattr, getxattr, etc.). + * + * Covers SOCK_STREAM, SOCK_DGRAM, and SOCK_SEQPACKET socket types. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../kselftest_harness.h" + +#define TEST_XATTR_NAME "user.testattr" +#define TEST_XATTR_VALUE "testvalue" +#define TEST_XATTR_VALUE2 "newvalue" + +/* + * Fixture for path-based Unix domain socket tests. + * Creates a SOCK_STREAM socket bound to a path in /tmp (typically tmpfs). + */ +FIXTURE(xattr_socket) +{ + char socket_path[PATH_MAX]; + int sockfd; +}; + +FIXTURE_VARIANT(xattr_socket) +{ + int sock_type; + const char *name; +}; + +FIXTURE_VARIANT_ADD(xattr_socket, stream) { + .sock_type =3D SOCK_STREAM, + .name =3D "stream", +}; + +FIXTURE_VARIANT_ADD(xattr_socket, dgram) { + .sock_type =3D SOCK_DGRAM, + .name =3D "dgram", +}; + +FIXTURE_VARIANT_ADD(xattr_socket, seqpacket) { + .sock_type =3D SOCK_SEQPACKET, + .name =3D "seqpacket", +}; + +FIXTURE_SETUP(xattr_socket) +{ + struct sockaddr_un addr; + int ret; + + self->sockfd =3D -1; + + snprintf(self->socket_path, sizeof(self->socket_path), + "/tmp/xattr_socket_test_%s.%d", variant->name, getpid()); + unlink(self->socket_path); + + self->sockfd =3D socket(AF_UNIX, variant->sock_type, 0); + ASSERT_GE(self->sockfd, 0) { + TH_LOG("Failed to create socket: %s", strerror(errno)); + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family =3D AF_UNIX; + strncpy(addr.sun_path, self->socket_path, sizeof(addr.sun_path) - 1); + + ret =3D bind(self->sockfd, (struct sockaddr *)&addr, sizeof(addr)); + ASSERT_EQ(ret, 0) { + TH_LOG("Failed to bind socket to %s: %s", + self->socket_path, strerror(errno)); + } +} + +FIXTURE_TEARDOWN(xattr_socket) +{ + if (self->sockfd >=3D 0) + close(self->sockfd); + unlink(self->socket_path); +} + +TEST_F(xattr_socket, set_user_xattr) +{ + int ret; + + ret =3D setxattr(self->socket_path, TEST_XATTR_NAME, + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); + ASSERT_EQ(ret, 0) { + TH_LOG("setxattr failed: %s (errno=3D%d)", strerror(errno), errno); + } +} + +TEST_F(xattr_socket, get_user_xattr) +{ + char buf[256]; + ssize_t ret; + + ret =3D setxattr(self->socket_path, TEST_XATTR_NAME, + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); + ASSERT_EQ(ret, 0) { + TH_LOG("setxattr failed: %s", strerror(errno)); + } + + memset(buf, 0, sizeof(buf)); + ret =3D getxattr(self->socket_path, TEST_XATTR_NAME, buf, sizeof(buf)); + ASSERT_EQ(ret, (ssize_t)strlen(TEST_XATTR_VALUE)) { + TH_LOG("getxattr returned %zd, expected %zu: %s", + ret, strlen(TEST_XATTR_VALUE), strerror(errno)); + } + ASSERT_STREQ(buf, TEST_XATTR_VALUE); +} + +TEST_F(xattr_socket, list_user_xattr) +{ + char list[1024]; + ssize_t ret; + bool found =3D false; + char *ptr; + + ret =3D setxattr(self->socket_path, TEST_XATTR_NAME, + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); + ASSERT_EQ(ret, 0) { + TH_LOG("setxattr failed: %s", strerror(errno)); + } + + memset(list, 0, sizeof(list)); + ret =3D listxattr(self->socket_path, list, sizeof(list)); + ASSERT_GT(ret, 0) { + TH_LOG("listxattr failed: %s", strerror(errno)); + } + + for (ptr =3D list; ptr < list + ret; ptr +=3D strlen(ptr) + 1) { + if (strcmp(ptr, TEST_XATTR_NAME) =3D=3D 0) { + found =3D true; + break; + } + } + ASSERT_TRUE(found) { + TH_LOG("xattr %s not found in list", TEST_XATTR_NAME); + } +} + +TEST_F(xattr_socket, remove_user_xattr) +{ + char buf[256]; + ssize_t ret; + + ret =3D setxattr(self->socket_path, TEST_XATTR_NAME, + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); + ASSERT_EQ(ret, 0) { + TH_LOG("setxattr failed: %s", strerror(errno)); + } + + ret =3D removexattr(self->socket_path, TEST_XATTR_NAME); + ASSERT_EQ(ret, 0) { + TH_LOG("removexattr failed: %s", strerror(errno)); + } + + ret =3D getxattr(self->socket_path, TEST_XATTR_NAME, buf, sizeof(buf)); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, ENODATA) { + TH_LOG("Expected ENODATA, got %s", strerror(errno)); + } +} + +/* + * Test that xattrs persist across socket close and reopen. + * The xattr is on the filesystem inode, not the socket fd. + */ +TEST_F(xattr_socket, xattr_persistence) +{ + char buf[256]; + ssize_t ret; + + ret =3D setxattr(self->socket_path, TEST_XATTR_NAME, + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); + ASSERT_EQ(ret, 0) { + TH_LOG("setxattr failed: %s", strerror(errno)); + } + + close(self->sockfd); + self->sockfd =3D -1; + + memset(buf, 0, sizeof(buf)); + ret =3D getxattr(self->socket_path, TEST_XATTR_NAME, buf, sizeof(buf)); + ASSERT_EQ(ret, (ssize_t)strlen(TEST_XATTR_VALUE)) { + TH_LOG("getxattr after close failed: %s", strerror(errno)); + } + ASSERT_STREQ(buf, TEST_XATTR_VALUE); +} + +TEST_F(xattr_socket, update_user_xattr) +{ + char buf[256]; + ssize_t ret; + + ret =3D setxattr(self->socket_path, TEST_XATTR_NAME, + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); + ASSERT_EQ(ret, 0); + + ret =3D setxattr(self->socket_path, TEST_XATTR_NAME, + TEST_XATTR_VALUE2, strlen(TEST_XATTR_VALUE2), 0); + ASSERT_EQ(ret, 0); + + memset(buf, 0, sizeof(buf)); + ret =3D getxattr(self->socket_path, TEST_XATTR_NAME, buf, sizeof(buf)); + ASSERT_EQ(ret, (ssize_t)strlen(TEST_XATTR_VALUE2)); + ASSERT_STREQ(buf, TEST_XATTR_VALUE2); +} + +TEST_F(xattr_socket, xattr_create_flag) +{ + int ret; + + ret =3D setxattr(self->socket_path, TEST_XATTR_NAME, + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); + ASSERT_EQ(ret, 0); + + ret =3D setxattr(self->socket_path, TEST_XATTR_NAME, + TEST_XATTR_VALUE2, strlen(TEST_XATTR_VALUE2), XATTR_CREATE); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, EEXIST); +} + +TEST_F(xattr_socket, xattr_replace_flag) +{ + int ret; + + ret =3D setxattr(self->socket_path, TEST_XATTR_NAME, + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), XATTR_REPLACE); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, ENODATA); +} + +TEST_F(xattr_socket, multiple_xattrs) +{ + char buf[256]; + ssize_t ret; + int i; + char name[64], value[64]; + const int num_xattrs =3D 5; + + for (i =3D 0; i < num_xattrs; i++) { + snprintf(name, sizeof(name), "user.test%d", i); + snprintf(value, sizeof(value), "value%d", i); + ret =3D setxattr(self->socket_path, name, value, strlen(value), 0); + ASSERT_EQ(ret, 0) { + TH_LOG("setxattr %s failed: %s", name, strerror(errno)); + } + } + + for (i =3D 0; i < num_xattrs; i++) { + snprintf(name, sizeof(name), "user.test%d", i); + snprintf(value, sizeof(value), "value%d", i); + memset(buf, 0, sizeof(buf)); + ret =3D getxattr(self->socket_path, name, buf, sizeof(buf)); + ASSERT_EQ(ret, (ssize_t)strlen(value)); + ASSERT_STREQ(buf, value); + } +} + +TEST_F(xattr_socket, xattr_empty_value) +{ + char buf[256]; + ssize_t ret; + + ret =3D setxattr(self->socket_path, TEST_XATTR_NAME, "", 0, 0); + ASSERT_EQ(ret, 0); + + ret =3D getxattr(self->socket_path, TEST_XATTR_NAME, buf, sizeof(buf)); + ASSERT_EQ(ret, 0); +} + +TEST_F(xattr_socket, xattr_get_size) +{ + ssize_t ret; + + ret =3D setxattr(self->socket_path, TEST_XATTR_NAME, + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); + ASSERT_EQ(ret, 0); + + ret =3D getxattr(self->socket_path, TEST_XATTR_NAME, NULL, 0); + ASSERT_EQ(ret, (ssize_t)strlen(TEST_XATTR_VALUE)); +} + +TEST_F(xattr_socket, xattr_buffer_too_small) +{ + char buf[2]; + ssize_t ret; + + ret =3D setxattr(self->socket_path, TEST_XATTR_NAME, + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); + ASSERT_EQ(ret, 0); + + ret =3D getxattr(self->socket_path, TEST_XATTR_NAME, buf, sizeof(buf)); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, ERANGE); +} + +TEST_F(xattr_socket, xattr_nonexistent) +{ + char buf[256]; + ssize_t ret; + + ret =3D getxattr(self->socket_path, "user.nonexistent", buf, sizeof(buf)); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, ENODATA); +} + +TEST_F(xattr_socket, remove_nonexistent_xattr) +{ + int ret; + + ret =3D removexattr(self->socket_path, "user.nonexistent"); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, ENODATA); +} + +TEST_F(xattr_socket, large_xattr_value) +{ + char large_value[4096]; + char read_buf[4096]; + ssize_t ret; + + memset(large_value, 'A', sizeof(large_value)); + + ret =3D setxattr(self->socket_path, TEST_XATTR_NAME, + large_value, sizeof(large_value), 0); + ASSERT_EQ(ret, 0) { + TH_LOG("setxattr with large value failed: %s", strerror(errno)); + } + + memset(read_buf, 0, sizeof(read_buf)); + ret =3D getxattr(self->socket_path, TEST_XATTR_NAME, + read_buf, sizeof(read_buf)); + ASSERT_EQ(ret, (ssize_t)sizeof(large_value)); + ASSERT_EQ(memcmp(large_value, read_buf, sizeof(large_value)), 0); +} + +/* + * Test lsetxattr/lgetxattr (don't follow symlinks). + * Socket files aren't symlinks, so this should work the same. + */ +TEST_F(xattr_socket, lsetxattr_lgetxattr) +{ + char buf[256]; + ssize_t ret; + + ret =3D lsetxattr(self->socket_path, TEST_XATTR_NAME, + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); + ASSERT_EQ(ret, 0) { + TH_LOG("lsetxattr failed: %s", strerror(errno)); + } + + memset(buf, 0, sizeof(buf)); + ret =3D lgetxattr(self->socket_path, TEST_XATTR_NAME, buf, sizeof(buf)); + ASSERT_EQ(ret, (ssize_t)strlen(TEST_XATTR_VALUE)); + ASSERT_STREQ(buf, TEST_XATTR_VALUE); +} + +/* + * Fixture for trusted.* xattr tests. + * These require CAP_SYS_ADMIN. + */ +FIXTURE(xattr_socket_trusted) +{ + char socket_path[PATH_MAX]; + int sockfd; +}; + +FIXTURE_VARIANT(xattr_socket_trusted) +{ + int sock_type; + const char *name; +}; + +FIXTURE_VARIANT_ADD(xattr_socket_trusted, stream) { + .sock_type =3D SOCK_STREAM, + .name =3D "stream", +}; + +FIXTURE_VARIANT_ADD(xattr_socket_trusted, dgram) { + .sock_type =3D SOCK_DGRAM, + .name =3D "dgram", +}; + +FIXTURE_VARIANT_ADD(xattr_socket_trusted, seqpacket) { + .sock_type =3D SOCK_SEQPACKET, + .name =3D "seqpacket", +}; + +FIXTURE_SETUP(xattr_socket_trusted) +{ + struct sockaddr_un addr; + int ret; + + self->sockfd =3D -1; + + snprintf(self->socket_path, sizeof(self->socket_path), + "/tmp/xattr_socket_trusted_%s.%d", variant->name, getpid()); + unlink(self->socket_path); + + self->sockfd =3D socket(AF_UNIX, variant->sock_type, 0); + ASSERT_GE(self->sockfd, 0); + + memset(&addr, 0, sizeof(addr)); + addr.sun_family =3D AF_UNIX; + strncpy(addr.sun_path, self->socket_path, sizeof(addr.sun_path) - 1); + + ret =3D bind(self->sockfd, (struct sockaddr *)&addr, sizeof(addr)); + ASSERT_EQ(ret, 0); +} + +FIXTURE_TEARDOWN(xattr_socket_trusted) +{ + if (self->sockfd >=3D 0) + close(self->sockfd); + unlink(self->socket_path); +} + +TEST_F(xattr_socket_trusted, set_trusted_xattr) +{ + char buf[256]; + ssize_t len; + int ret; + + ret =3D setxattr(self->socket_path, "trusted.testattr", + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); + if (ret =3D=3D -1 && errno =3D=3D EPERM) + SKIP(return, "Need CAP_SYS_ADMIN for trusted.* xattrs"); + ASSERT_EQ(ret, 0) { + TH_LOG("setxattr trusted.testattr failed: %s", strerror(errno)); + } + + memset(buf, 0, sizeof(buf)); + len =3D getxattr(self->socket_path, "trusted.testattr", + buf, sizeof(buf)); + ASSERT_EQ(len, (ssize_t)strlen(TEST_XATTR_VALUE)); + ASSERT_STREQ(buf, TEST_XATTR_VALUE); +} + +TEST_F(xattr_socket_trusted, get_trusted_xattr_unprivileged) +{ + char buf[256]; + ssize_t ret; + + ret =3D getxattr(self->socket_path, "trusted.testattr", buf, sizeof(buf)); + ASSERT_EQ(ret, -1); + ASSERT_TRUE(errno =3D=3D ENODATA || errno =3D=3D EPERM) { + TH_LOG("Expected ENODATA or EPERM, got %s", strerror(errno)); + } +} + +TEST_HARNESS_MAIN --=20 2.47.3 From nobody Thu Mar 19 02:04:58 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CFB6F3101A3; Mon, 16 Feb 2026 13:33:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771248786; cv=none; b=V53ontN1YPcWcQX+laz5S/k/DNntD8zcl7mmxeH6NUO2yWkIeuVbiEoHb8/tWXW5UFtgJrJybTpz89ip+jxxO6WOdqsPCE9YYx7kAe7p0srOWlpq8SW5sA/taAMBGitxW1p5FRqqekWZ2Few2QlqquEV1sTFLpTKuNjVIAJ8g34= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771248786; c=relaxed/simple; bh=nPMJv52b+iIBwowiKMG1azbuD44a7hHTFx5KxGAnUcM=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=i4fKXzFISC6N0Uz/i/9vSe2UQG/LDogXHtYQHy1UOpEkibLADYnX+N6Xpx06Qq0VmOOxrGq33y3BCca+VsryMM4uIJERLA83A0M98m4t4+oO8lc5/9wZsusrPMA+zwOugMm04qS7FsqApTSYUCxC7Tb9J7c6v6kDkqdf1iANRQQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=fV3IfwRb; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="fV3IfwRb" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 4B3F0C19424; Mon, 16 Feb 2026 13:33:03 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1771248786; bh=nPMJv52b+iIBwowiKMG1azbuD44a7hHTFx5KxGAnUcM=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=fV3IfwRb6d1jbCeVxB6PkY4ivmmysujLKj5ZaqrrLALmNYk69zYkUmiEfRnRa/mId 3j7LS24QsNw/0iJw00yIbgRDay6BnIDtm1hDog0u59LIepmOpHXIOHgd0hwgJdmuBI Gwv3jGYuusBrG09DxkEP5aGHuPIPubuExRnHGNBJmMmMSAOOBoqwPwguqTuDyCyOEP JA6Uwh2JGG+M1W0htA8dJuD14svtgiVc3eisNOWTOlzMzO2DY5EXyC+E8aqi3APCdh MoBRaQKtGlVLJe89KgL6zvGkdqu1ATNCTkjNN4/Hnq1sO5m+xjOE26iyIRRMeL27hQ uGqoXobd83WiA== From: Christian Brauner Date: Mon, 16 Feb 2026 14:32:09 +0100 Subject: [PATCH 13/14] selftests/xattr: sockfs socket xattr tests Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260216-work-xattr-socket-v1-13-c2efa4f74cb7@kernel.org> References: <20260216-work-xattr-socket-v1-0-c2efa4f74cb7@kernel.org> In-Reply-To: <20260216-work-xattr-socket-v1-0-c2efa4f74cb7@kernel.org> To: linux-fsdevel@vger.kernel.org Cc: Jeff Layton , Josef Bacik , Alexander Viro , Jan Kara , linux-kernel@vger.kernel.org, Hugh Dickins , linux-mm@kvack.org, Greg Kroah-Hartman , Tejun Heo , Eric Dumazet , Jakub Kicinski , Jann Horn , netdev@vger.kernel.org, Christian Brauner X-Mailer: b4 0.15-dev-47773 X-Developer-Signature: v=1; a=openpgp-sha256; l=11906; i=brauner@kernel.org; h=from:subject:message-id; bh=nPMJv52b+iIBwowiKMG1azbuD44a7hHTFx5KxGAnUcM=; b=owGbwMvMwCU28Zj0gdSKO4sYT6slMWROlom79bXAWlx6913rXc5Xu2ODZtUmyDdtfFe12Nyea fLD3tl7O0pZGMS4GGTFFFkc2k3C5ZbzVGw2ytSAmcPKBDKEgYtTACby/hYjwxf7EEm9LVJ/X35n +L9P7uvcI5Hr5L4l5erenHumOF7/Wy7D/6CpC/VyEy2+NSqndjE+2zFVbP+pSWsMF1joT48oKyi L4QEA X-Developer-Key: i=brauner@kernel.org; a=openpgp; fpr=4880B8C9BD0E5106FC070F4F7B3C391EFEA93624 Test user.* extended attribute operations on sockfs sockets. Sockets created via socket() have their inodes in sockfs, which now supports user.* xattrs with per-inode limits. Tests fsetxattr/fgetxattr/flistxattr/fremovexattr operations including set/get, listing (verifies system.sockprotoname presence), remove, update, XATTR_CREATE/XATTR_REPLACE flags, empty values, size queries, and buffer-too-small errors. Also tests per-inode limit enforcement: maximum 128 xattrs, maximum 128KB total value size, limit recovery after removal, and independent limits across different sockets. Signed-off-by: Christian Brauner Acked-by: "Darrick J. Wong" Reviewed-by: Jan Kara Reviewed-by: Jeff Layton --- .../testing/selftests/filesystems/xattr/.gitignore | 1 + tools/testing/selftests/filesystems/xattr/Makefile | 2 +- .../filesystems/xattr/xattr_sockfs_test.c | 363 +++++++++++++++++= ++++ 3 files changed, 365 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/filesystems/xattr/.gitignore b/tools/t= esting/selftests/filesystems/xattr/.gitignore index 5fd015d2257a..00a59c89efab 100644 --- a/tools/testing/selftests/filesystems/xattr/.gitignore +++ b/tools/testing/selftests/filesystems/xattr/.gitignore @@ -1 +1,2 @@ xattr_socket_test +xattr_sockfs_test diff --git a/tools/testing/selftests/filesystems/xattr/Makefile b/tools/tes= ting/selftests/filesystems/xattr/Makefile index e3d8dca80faa..2cd722dba47b 100644 --- a/tools/testing/selftests/filesystems/xattr/Makefile +++ b/tools/testing/selftests/filesystems/xattr/Makefile @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 =20 CFLAGS +=3D $(KHDR_INCLUDES) -TEST_GEN_PROGS :=3D xattr_socket_test +TEST_GEN_PROGS :=3D xattr_socket_test xattr_sockfs_test =20 include ../../lib.mk diff --git a/tools/testing/selftests/filesystems/xattr/xattr_sockfs_test.c = b/tools/testing/selftests/filesystems/xattr/xattr_sockfs_test.c new file mode 100644 index 000000000000..b4824b01a86d --- /dev/null +++ b/tools/testing/selftests/filesystems/xattr/xattr_sockfs_test.c @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2026 Christian Brauner +/* + * Test extended attributes on sockfs sockets. + * + * Sockets created via socket() have their inodes in sockfs, which supports + * user.* xattrs with per-inode limits: up to 128 xattrs and 128KB total + * value size. These tests verify xattr operations via fsetxattr/fgetxattr/ + * flistxattr/fremovexattr on the socket fd, as well as limit enforcement. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../kselftest_harness.h" + +#define TEST_XATTR_NAME "user.testattr" +#define TEST_XATTR_VALUE "testvalue" +#define TEST_XATTR_VALUE2 "newvalue" + +/* Per-inode limits for user.* xattrs on sockfs (from include/linux/xattr.= h) */ +#define SIMPLE_XATTR_MAX_NR 128 +#define SIMPLE_XATTR_MAX_SIZE (128 << 10) /* 128 KB */ + +#ifndef XATTR_SIZE_MAX +#define XATTR_SIZE_MAX 65536 +#endif + +/* + * Fixture for sockfs socket xattr tests. + * Creates an AF_UNIX socket (lives in sockfs, not bound to any path). + */ +FIXTURE(xattr_sockfs) +{ + int sockfd; +}; + +FIXTURE_SETUP(xattr_sockfs) +{ + self->sockfd =3D socket(AF_UNIX, SOCK_STREAM, 0); + ASSERT_GE(self->sockfd, 0) { + TH_LOG("Failed to create socket: %s", strerror(errno)); + } +} + +FIXTURE_TEARDOWN(xattr_sockfs) +{ + if (self->sockfd >=3D 0) + close(self->sockfd); +} + +TEST_F(xattr_sockfs, set_get_user_xattr) +{ + char buf[256]; + ssize_t ret; + + ret =3D fsetxattr(self->sockfd, TEST_XATTR_NAME, + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); + ASSERT_EQ(ret, 0) { + TH_LOG("fsetxattr failed: %s", strerror(errno)); + } + + memset(buf, 0, sizeof(buf)); + ret =3D fgetxattr(self->sockfd, TEST_XATTR_NAME, buf, sizeof(buf)); + ASSERT_EQ(ret, (ssize_t)strlen(TEST_XATTR_VALUE)) { + TH_LOG("fgetxattr returned %zd: %s", ret, strerror(errno)); + } + ASSERT_STREQ(buf, TEST_XATTR_VALUE); +} + +/* + * Test listing xattrs on a sockfs socket. + * Should include user.* xattrs and system.sockprotoname. + */ +TEST_F(xattr_sockfs, list_user_xattr) +{ + char list[4096]; + ssize_t ret; + char *ptr; + bool found_user =3D false; + bool found_proto =3D false; + + ret =3D fsetxattr(self->sockfd, TEST_XATTR_NAME, + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); + ASSERT_EQ(ret, 0) { + TH_LOG("fsetxattr failed: %s", strerror(errno)); + } + + memset(list, 0, sizeof(list)); + ret =3D flistxattr(self->sockfd, list, sizeof(list)); + ASSERT_GT(ret, 0) { + TH_LOG("flistxattr failed: %s", strerror(errno)); + } + + for (ptr =3D list; ptr < list + ret; ptr +=3D strlen(ptr) + 1) { + if (strcmp(ptr, TEST_XATTR_NAME) =3D=3D 0) + found_user =3D true; + if (strcmp(ptr, "system.sockprotoname") =3D=3D 0) + found_proto =3D true; + } + ASSERT_TRUE(found_user) { + TH_LOG("user xattr not found in list"); + } + ASSERT_TRUE(found_proto) { + TH_LOG("system.sockprotoname not found in list"); + } +} + +TEST_F(xattr_sockfs, remove_user_xattr) +{ + char buf[256]; + ssize_t ret; + + ret =3D fsetxattr(self->sockfd, TEST_XATTR_NAME, + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); + ASSERT_EQ(ret, 0); + + ret =3D fremovexattr(self->sockfd, TEST_XATTR_NAME); + ASSERT_EQ(ret, 0) { + TH_LOG("fremovexattr failed: %s", strerror(errno)); + } + + ret =3D fgetxattr(self->sockfd, TEST_XATTR_NAME, buf, sizeof(buf)); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, ENODATA); +} + +TEST_F(xattr_sockfs, update_user_xattr) +{ + char buf[256]; + ssize_t ret; + + ret =3D fsetxattr(self->sockfd, TEST_XATTR_NAME, + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); + ASSERT_EQ(ret, 0); + + ret =3D fsetxattr(self->sockfd, TEST_XATTR_NAME, + TEST_XATTR_VALUE2, strlen(TEST_XATTR_VALUE2), 0); + ASSERT_EQ(ret, 0); + + memset(buf, 0, sizeof(buf)); + ret =3D fgetxattr(self->sockfd, TEST_XATTR_NAME, buf, sizeof(buf)); + ASSERT_EQ(ret, (ssize_t)strlen(TEST_XATTR_VALUE2)); + ASSERT_STREQ(buf, TEST_XATTR_VALUE2); +} + +TEST_F(xattr_sockfs, xattr_create_flag) +{ + int ret; + + ret =3D fsetxattr(self->sockfd, TEST_XATTR_NAME, + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); + ASSERT_EQ(ret, 0); + + ret =3D fsetxattr(self->sockfd, TEST_XATTR_NAME, + TEST_XATTR_VALUE2, strlen(TEST_XATTR_VALUE2), + XATTR_CREATE); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, EEXIST); +} + +TEST_F(xattr_sockfs, xattr_replace_flag) +{ + int ret; + + ret =3D fsetxattr(self->sockfd, TEST_XATTR_NAME, + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), + XATTR_REPLACE); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, ENODATA); +} + +TEST_F(xattr_sockfs, get_nonexistent) +{ + char buf[256]; + ssize_t ret; + + ret =3D fgetxattr(self->sockfd, "user.nonexistent", buf, sizeof(buf)); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, ENODATA); +} + +TEST_F(xattr_sockfs, empty_value) +{ + ssize_t ret; + + ret =3D fsetxattr(self->sockfd, TEST_XATTR_NAME, "", 0, 0); + ASSERT_EQ(ret, 0); + + ret =3D fgetxattr(self->sockfd, TEST_XATTR_NAME, NULL, 0); + ASSERT_EQ(ret, 0); +} + +TEST_F(xattr_sockfs, get_size) +{ + ssize_t ret; + + ret =3D fsetxattr(self->sockfd, TEST_XATTR_NAME, + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); + ASSERT_EQ(ret, 0); + + ret =3D fgetxattr(self->sockfd, TEST_XATTR_NAME, NULL, 0); + ASSERT_EQ(ret, (ssize_t)strlen(TEST_XATTR_VALUE)); +} + +TEST_F(xattr_sockfs, buffer_too_small) +{ + char buf[2]; + ssize_t ret; + + ret =3D fsetxattr(self->sockfd, TEST_XATTR_NAME, + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); + ASSERT_EQ(ret, 0); + + ret =3D fgetxattr(self->sockfd, TEST_XATTR_NAME, buf, sizeof(buf)); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, ERANGE); +} + +/* + * Test maximum number of user.* xattrs per socket. + * The kernel enforces SIMPLE_XATTR_MAX_NR (128), so the 129th should + * fail with ENOSPC. + */ +TEST_F(xattr_sockfs, max_nr_xattrs) +{ + char name[32]; + int i, ret; + + for (i =3D 0; i < SIMPLE_XATTR_MAX_NR; i++) { + snprintf(name, sizeof(name), "user.test%03d", i); + ret =3D fsetxattr(self->sockfd, name, "v", 1, 0); + ASSERT_EQ(ret, 0) { + TH_LOG("fsetxattr %s failed at i=3D%d: %s", + name, i, strerror(errno)); + } + } + + ret =3D fsetxattr(self->sockfd, "user.overflow", "v", 1, 0); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, ENOSPC) { + TH_LOG("Expected ENOSPC for xattr %d, got %s", + SIMPLE_XATTR_MAX_NR + 1, strerror(errno)); + } +} + +/* + * Test maximum total value size for user.* xattrs. + * The kernel enforces SIMPLE_XATTR_MAX_SIZE (128KB). Individual xattr + * values are limited to XATTR_SIZE_MAX (64KB) by the VFS, so we need + * at least two xattrs to hit the total limit. + */ +TEST_F(xattr_sockfs, max_xattr_size) +{ + char *value; + int ret; + + value =3D malloc(XATTR_SIZE_MAX); + ASSERT_NE(value, NULL); + memset(value, 'A', XATTR_SIZE_MAX); + + /* First 64KB xattr - total =3D 64KB */ + ret =3D fsetxattr(self->sockfd, "user.big1", value, XATTR_SIZE_MAX, 0); + ASSERT_EQ(ret, 0) { + TH_LOG("first large xattr failed: %s", strerror(errno)); + } + + /* Second 64KB xattr - total =3D 128KB (exactly at limit) */ + ret =3D fsetxattr(self->sockfd, "user.big2", value, XATTR_SIZE_MAX, 0); + free(value); + ASSERT_EQ(ret, 0) { + TH_LOG("second large xattr failed: %s", strerror(errno)); + } + + /* Third xattr with 1 byte - total > 128KB, should fail */ + ret =3D fsetxattr(self->sockfd, "user.big3", "v", 1, 0); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, ENOSPC) { + TH_LOG("Expected ENOSPC when exceeding size limit, got %s", + strerror(errno)); + } +} + +/* + * Test that removing an xattr frees limit space, allowing re-addition. + */ +TEST_F(xattr_sockfs, limit_remove_readd) +{ + char name[32]; + int i, ret; + + /* Fill up to the maximum count */ + for (i =3D 0; i < SIMPLE_XATTR_MAX_NR; i++) { + snprintf(name, sizeof(name), "user.test%03d", i); + ret =3D fsetxattr(self->sockfd, name, "v", 1, 0); + ASSERT_EQ(ret, 0); + } + + /* Verify we're at the limit */ + ret =3D fsetxattr(self->sockfd, "user.overflow", "v", 1, 0); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, ENOSPC); + + /* Remove one xattr */ + ret =3D fremovexattr(self->sockfd, "user.test000"); + ASSERT_EQ(ret, 0); + + /* Now we should be able to add one more */ + ret =3D fsetxattr(self->sockfd, "user.newattr", "v", 1, 0); + ASSERT_EQ(ret, 0) { + TH_LOG("re-add after remove failed: %s", strerror(errno)); + } +} + +/* + * Test that two different sockets have independent xattr limits. + */ +TEST_F(xattr_sockfs, limits_per_inode) +{ + char buf[256]; + int sock2; + ssize_t ret; + + sock2 =3D socket(AF_UNIX, SOCK_STREAM, 0); + ASSERT_GE(sock2, 0); + + /* Set xattr on first socket */ + ret =3D fsetxattr(self->sockfd, TEST_XATTR_NAME, + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); + ASSERT_EQ(ret, 0); + + /* First socket's xattr should not be visible on second socket */ + ret =3D fgetxattr(sock2, TEST_XATTR_NAME, NULL, 0); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, ENODATA); + + /* Second socket should independently accept xattrs */ + ret =3D fsetxattr(sock2, TEST_XATTR_NAME, + TEST_XATTR_VALUE2, strlen(TEST_XATTR_VALUE2), 0); + ASSERT_EQ(ret, 0); + + /* Verify each socket has its own value */ + memset(buf, 0, sizeof(buf)); + ret =3D fgetxattr(self->sockfd, TEST_XATTR_NAME, buf, sizeof(buf)); + ASSERT_EQ(ret, (ssize_t)strlen(TEST_XATTR_VALUE)); + ASSERT_STREQ(buf, TEST_XATTR_VALUE); + + memset(buf, 0, sizeof(buf)); + ret =3D fgetxattr(sock2, TEST_XATTR_NAME, buf, sizeof(buf)); + ASSERT_EQ(ret, (ssize_t)strlen(TEST_XATTR_VALUE2)); + ASSERT_STREQ(buf, TEST_XATTR_VALUE2); + + close(sock2); +} + +TEST_HARNESS_MAIN --=20 2.47.3 From nobody Thu Mar 19 02:04:58 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5DA2A3101D2; Mon, 16 Feb 2026 13:33:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771248790; cv=none; b=QAPlG5lIME0QL/SIte0hlXVU5JrE6jVMI2XD9NFgCAc86Y+EBpyZII6F4wQeA0zi6LjhVwOmqUbaLNSnfg5i+N8a8HeaAg3QdLTIHfVMLDo7Cz5xg+kJ9Wpps5MtJK0uoOMYJnV2kATJZEi3k/fvjWqJCvm8HKE0neC9sUnl/Fk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771248790; c=relaxed/simple; bh=hvRp6kqMtP01OL7JzalGLx5e4r/2rC/9MUn7FHvN4XY=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=idvFit61RYP1Xyktd7M/h8y5Yj9laNqOtuWK5Ol4s7mtC3fe/qLR+QShb8ezetHdCEiWT1ERIeUK6gdolYgaMcoR0U/JuzvuPjOd/+BCUSYIIdlrRJwc/2FEwcwJrAsBsyUy1SxfGnC0klgz0v7QdTUcpjFU5PwqPaF9IUvJBeE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=ksVRT/TC; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="ksVRT/TC" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 07EADC19423; Mon, 16 Feb 2026 13:33:06 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1771248790; bh=hvRp6kqMtP01OL7JzalGLx5e4r/2rC/9MUn7FHvN4XY=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=ksVRT/TC3Qs/BDIBZ4Xwz+odoJUU6U4EIeZ15q9gpGsQphl0RvHMnhrcx00O/o44L u8TStq3seKruuRWTCAfpzOHfvucMwPSX/C97ZOFdF4/BgJ2k5CasvzjAlBlIz8PnGE xM/FeirGoDXBN6jq/9WeT3f3VLf/NQ0Pd1cSjE8XARMfC5mF+5PbhiRTClUXTLwL+M Y0hFvkeZF34qG+L0Xjh4pF4S+FZRbyYI0oSk4PwJRBtMWT/ASvV16Mf/NyHc0GNp0V 3GVIfwPUoEmJwb3gr4GnWFSDE/pu+9wKia7m2g/O1iqCXPDbyd8Qe0i+qw3gapxDEA xbK4hkdbLLewQ== From: Christian Brauner Date: Mon, 16 Feb 2026 14:32:10 +0100 Subject: [PATCH 14/14] selftests/xattr: test xattrs on various socket families Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260216-work-xattr-socket-v1-14-c2efa4f74cb7@kernel.org> References: <20260216-work-xattr-socket-v1-0-c2efa4f74cb7@kernel.org> In-Reply-To: <20260216-work-xattr-socket-v1-0-c2efa4f74cb7@kernel.org> To: linux-fsdevel@vger.kernel.org Cc: Jeff Layton , Josef Bacik , Alexander Viro , Jan Kara , linux-kernel@vger.kernel.org, Hugh Dickins , linux-mm@kvack.org, Greg Kroah-Hartman , Tejun Heo , Eric Dumazet , Jakub Kicinski , Jann Horn , netdev@vger.kernel.org, Christian Brauner X-Mailer: b4 0.15-dev-47773 X-Developer-Signature: v=1; a=openpgp-sha256; l=6636; i=brauner@kernel.org; h=from:subject:message-id; bh=hvRp6kqMtP01OL7JzalGLx5e4r/2rC/9MUn7FHvN4XY=; b=owGbwMvMwCU28Zj0gdSKO4sYT6slMWROlokr042WWq769uIti0fds8S7VFz8zzcGv+fTK5kk1 xG/XdC1o5SFQYyLQVZMkcWh3SRcbjlPxWajTA2YOaxMIEMYuDgFYCJCHIwML3UXZu86bHnFwruo 6I2KarM716f/Dv4mfcYbKyuezPq8h+GvdEPpDo5JyQ9slPgXCLvfeSyufcV+SvqkGXd2ZKy1YH/ LCAA= X-Developer-Key: i=brauner@kernel.org; a=openpgp; fpr=4880B8C9BD0E5106FC070F4F7B3C391EFEA93624 Test user.* xattr operations on sockets from different address families: AF_INET, AF_INET6, AF_NETLINK, and AF_PACKET. All socket types use sockfs for their inodes, so user.* xattrs should work regardless of address family. Each fixture creates a socket (no bind needed) and verifies the full fsetxattr/fgetxattr/flistxattr/fremovexattr cycle. AF_INET6 skips if not supported; AF_PACKET skips if CAP_NET_RAW is unavailable. Also tests abstract namespace AF_UNIX sockets, which live in sockfs (not on a filesystem) and should support user.* xattrs. Signed-off-by: Christian Brauner Acked-by: "Darrick J. Wong" Reviewed-by: Jan Kara Reviewed-by: Jeff Layton --- .../testing/selftests/filesystems/xattr/.gitignore | 1 + tools/testing/selftests/filesystems/xattr/Makefile | 2 +- .../filesystems/xattr/xattr_socket_types_test.c | 177 +++++++++++++++++= ++++ 3 files changed, 179 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/filesystems/xattr/.gitignore b/tools/t= esting/selftests/filesystems/xattr/.gitignore index 00a59c89efab..092d14094c0f 100644 --- a/tools/testing/selftests/filesystems/xattr/.gitignore +++ b/tools/testing/selftests/filesystems/xattr/.gitignore @@ -1,2 +1,3 @@ xattr_socket_test xattr_sockfs_test +xattr_socket_types_test diff --git a/tools/testing/selftests/filesystems/xattr/Makefile b/tools/tes= ting/selftests/filesystems/xattr/Makefile index 2cd722dba47b..95364ffb10e9 100644 --- a/tools/testing/selftests/filesystems/xattr/Makefile +++ b/tools/testing/selftests/filesystems/xattr/Makefile @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 =20 CFLAGS +=3D $(KHDR_INCLUDES) -TEST_GEN_PROGS :=3D xattr_socket_test xattr_sockfs_test +TEST_GEN_PROGS :=3D xattr_socket_test xattr_sockfs_test xattr_socket_types= _test =20 include ../../lib.mk diff --git a/tools/testing/selftests/filesystems/xattr/xattr_socket_types_t= est.c b/tools/testing/selftests/filesystems/xattr/xattr_socket_types_test.c new file mode 100644 index 000000000000..bfabe91b2ed1 --- /dev/null +++ b/tools/testing/selftests/filesystems/xattr/xattr_socket_types_test.c @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2026 Christian Brauner +/* + * Test user.* xattrs on various socket families. + * + * All socket types use sockfs for their inodes, so user.* xattrs should + * work on any socket regardless of address family. This tests AF_INET, + * AF_INET6, AF_NETLINK, AF_PACKET, and abstract namespace AF_UNIX sockets. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../kselftest_harness.h" + +#define TEST_XATTR_NAME "user.testattr" +#define TEST_XATTR_VALUE "testvalue" + +FIXTURE(xattr_socket_types) +{ + int sockfd; +}; + +FIXTURE_VARIANT(xattr_socket_types) +{ + int family; + int type; + int protocol; +}; + +FIXTURE_VARIANT_ADD(xattr_socket_types, inet) { + .family =3D AF_INET, + .type =3D SOCK_STREAM, + .protocol =3D 0, +}; + +FIXTURE_VARIANT_ADD(xattr_socket_types, inet6) { + .family =3D AF_INET6, + .type =3D SOCK_STREAM, + .protocol =3D 0, +}; + +FIXTURE_VARIANT_ADD(xattr_socket_types, netlink) { + .family =3D AF_NETLINK, + .type =3D SOCK_RAW, + .protocol =3D NETLINK_USERSOCK, +}; + +FIXTURE_VARIANT_ADD(xattr_socket_types, packet) { + .family =3D AF_PACKET, + .type =3D SOCK_DGRAM, + .protocol =3D 0, +}; + +FIXTURE_SETUP(xattr_socket_types) +{ + self->sockfd =3D socket(variant->family, variant->type, + variant->protocol); + if (self->sockfd < 0 && + (errno =3D=3D EAFNOSUPPORT || errno =3D=3D EPERM || errno =3D=3D EACC= ES)) + SKIP(return, "socket(%d, %d, %d) not available: %s", + variant->family, variant->type, variant->protocol, + strerror(errno)); + ASSERT_GE(self->sockfd, 0) { + TH_LOG("Failed to create socket(%d, %d, %d): %s", + variant->family, variant->type, variant->protocol, + strerror(errno)); + } +} + +FIXTURE_TEARDOWN(xattr_socket_types) +{ + if (self->sockfd >=3D 0) + close(self->sockfd); +} + +TEST_F(xattr_socket_types, set_get_list_remove) +{ + char buf[256], list[4096], *ptr; + ssize_t ret; + bool found; + + ret =3D fsetxattr(self->sockfd, TEST_XATTR_NAME, + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); + ASSERT_EQ(ret, 0) { + TH_LOG("fsetxattr failed: %s", strerror(errno)); + } + + memset(buf, 0, sizeof(buf)); + ret =3D fgetxattr(self->sockfd, TEST_XATTR_NAME, buf, sizeof(buf)); + ASSERT_EQ(ret, (ssize_t)strlen(TEST_XATTR_VALUE)); + ASSERT_STREQ(buf, TEST_XATTR_VALUE); + + memset(list, 0, sizeof(list)); + ret =3D flistxattr(self->sockfd, list, sizeof(list)); + ASSERT_GT(ret, 0); + found =3D false; + for (ptr =3D list; ptr < list + ret; ptr +=3D strlen(ptr) + 1) { + if (strcmp(ptr, TEST_XATTR_NAME) =3D=3D 0) + found =3D true; + } + ASSERT_TRUE(found); + + ret =3D fremovexattr(self->sockfd, TEST_XATTR_NAME); + ASSERT_EQ(ret, 0); + + ret =3D fgetxattr(self->sockfd, TEST_XATTR_NAME, buf, sizeof(buf)); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, ENODATA); +} + +/* + * Test abstract namespace AF_UNIX socket. + * Abstract sockets don't have a filesystem path; their inodes live in + * sockfs so user.* xattrs should work via fsetxattr/fgetxattr. + */ +FIXTURE(xattr_abstract) +{ + int sockfd; +}; + +FIXTURE_SETUP(xattr_abstract) +{ + struct sockaddr_un addr; + char name[64]; + int ret, len; + + self->sockfd =3D socket(AF_UNIX, SOCK_STREAM, 0); + ASSERT_GE(self->sockfd, 0); + + len =3D snprintf(name, sizeof(name), "xattr_test_abstract_%d", getpid()); + + memset(&addr, 0, sizeof(addr)); + addr.sun_family =3D AF_UNIX; + addr.sun_path[0] =3D '\0'; + memcpy(&addr.sun_path[1], name, len); + + ret =3D bind(self->sockfd, (struct sockaddr *)&addr, + offsetof(struct sockaddr_un, sun_path) + 1 + len); + ASSERT_EQ(ret, 0); +} + +FIXTURE_TEARDOWN(xattr_abstract) +{ + if (self->sockfd >=3D 0) + close(self->sockfd); +} + +TEST_F(xattr_abstract, set_get) +{ + char buf[256]; + ssize_t ret; + + ret =3D fsetxattr(self->sockfd, TEST_XATTR_NAME, + TEST_XATTR_VALUE, strlen(TEST_XATTR_VALUE), 0); + ASSERT_EQ(ret, 0) { + TH_LOG("fsetxattr on abstract socket failed: %s", + strerror(errno)); + } + + memset(buf, 0, sizeof(buf)); + ret =3D fgetxattr(self->sockfd, TEST_XATTR_NAME, buf, sizeof(buf)); + ASSERT_EQ(ret, (ssize_t)strlen(TEST_XATTR_VALUE)); + ASSERT_STREQ(buf, TEST_XATTR_VALUE); +} + +TEST_HARNESS_MAIN --=20 2.47.3