diff options
| author | Jeff Layton <jlayton@kernel.org> | 2026-03-25 10:40:30 -0400 |
|---|---|---|
| committer | Chuck Lever <chuck.lever@oracle.com> | 2026-05-28 11:31:26 -0400 |
| commit | 59302fe702de9d4b77aeea06c864145312eecc6e (patch) | |
| tree | 41dbb0d9a5834a1e3fd26e2aafa7aeeb2cb1329c /net | |
| parent | 823515ebaaf15a741a2098612cbed246682c16f2 (diff) | |
| download | linux-next-history-59302fe702de9d4b77aeea06c864145312eecc6e.tar.gz | |
sunrpc: add netlink upcall for the auth.unix.gid cache
Add netlink-based cache upcall support for the unix_gid (auth.unix.gid)
cache, using the sunrpc generic netlink family.
Add unix-gid attribute-set (seqno, uid, gids multi-attr, negative,
expiry), unix-gid-reqs wrapper, and unix-gid-get-reqs /
unix-gid-set-reqs operations to the sunrpc_cache YAML spec and
generated headers.
Implement sunrpc_nl_unix_gid_get_reqs_dumpit() which snapshots pending
unix_gid cache requests and sends each entry's seqno and uid over
netlink.
Implement sunrpc_nl_unix_gid_set_reqs_doit() which parses unix_gid
cache responses from userspace (uid, expiry, gids as u32 multi-attr
or negative flag) and updates the cache via unix_gid_lookup() /
sunrpc_cache_update().
Wire up unix_gid_notify() callback in unix_gid_cache_template so
cache misses trigger SUNRPC_CMD_CACHE_NOTIFY multicast events with
SUNRPC_CACHE_TYPE_UNIX_GID.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Diffstat (limited to 'net')
| -rw-r--r-- | net/sunrpc/netlink.c | 32 | ||||
| -rw-r--r-- | net/sunrpc/netlink.h | 5 | ||||
| -rw-r--r-- | net/sunrpc/svcauth_unix.c | 234 |
3 files changed, 271 insertions, 0 deletions
diff --git a/net/sunrpc/netlink.c b/net/sunrpc/netlink.c index f57eb17fc27df..41843f007c37a 100644 --- a/net/sunrpc/netlink.c +++ b/net/sunrpc/netlink.c @@ -32,6 +32,24 @@ static const struct nla_policy sunrpc_ip_map_set_reqs_nl_policy[SUNRPC_A_IP_MAP_ [SUNRPC_A_IP_MAP_REQS_REQUESTS] = NLA_POLICY_NESTED(sunrpc_ip_map_nl_policy), }; +const struct nla_policy sunrpc_unix_gid_nl_policy[SUNRPC_A_UNIX_GID_EXPIRY + 1] = { + [SUNRPC_A_UNIX_GID_SEQNO] = { .type = NLA_U64, }, + [SUNRPC_A_UNIX_GID_UID] = { .type = NLA_U32, }, + [SUNRPC_A_UNIX_GID_GIDS] = { .type = NLA_U32, }, + [SUNRPC_A_UNIX_GID_NEGATIVE] = { .type = NLA_FLAG, }, + [SUNRPC_A_UNIX_GID_EXPIRY] = { .type = NLA_U64, }, +}; + +/* SUNRPC_CMD_UNIX_GID_GET_REQS - dump */ +static const struct nla_policy sunrpc_unix_gid_get_reqs_nl_policy[SUNRPC_A_UNIX_GID_REQS_REQUESTS + 1] = { + [SUNRPC_A_UNIX_GID_REQS_REQUESTS] = NLA_POLICY_NESTED(sunrpc_unix_gid_nl_policy), +}; + +/* SUNRPC_CMD_UNIX_GID_SET_REQS - do */ +static const struct nla_policy sunrpc_unix_gid_set_reqs_nl_policy[SUNRPC_A_UNIX_GID_REQS_REQUESTS + 1] = { + [SUNRPC_A_UNIX_GID_REQS_REQUESTS] = NLA_POLICY_NESTED(sunrpc_unix_gid_nl_policy), +}; + /* Ops table for sunrpc */ static const struct genl_split_ops sunrpc_nl_ops[] = { { @@ -48,6 +66,20 @@ static const struct genl_split_ops sunrpc_nl_ops[] = { .maxattr = SUNRPC_A_IP_MAP_REQS_REQUESTS, .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, + { + .cmd = SUNRPC_CMD_UNIX_GID_GET_REQS, + .dumpit = sunrpc_nl_unix_gid_get_reqs_dumpit, + .policy = sunrpc_unix_gid_get_reqs_nl_policy, + .maxattr = SUNRPC_A_UNIX_GID_REQS_REQUESTS, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP, + }, + { + .cmd = SUNRPC_CMD_UNIX_GID_SET_REQS, + .doit = sunrpc_nl_unix_gid_set_reqs_doit, + .policy = sunrpc_unix_gid_set_reqs_nl_policy, + .maxattr = SUNRPC_A_UNIX_GID_REQS_REQUESTS, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, }; static const struct genl_multicast_group sunrpc_nl_mcgrps[] = { diff --git a/net/sunrpc/netlink.h b/net/sunrpc/netlink.h index 68b773960b397..16b87519a4096 100644 --- a/net/sunrpc/netlink.h +++ b/net/sunrpc/netlink.h @@ -14,11 +14,16 @@ /* Common nested types */ extern const struct nla_policy sunrpc_ip_map_nl_policy[SUNRPC_A_IP_MAP_EXPIRY + 1]; +extern const struct nla_policy sunrpc_unix_gid_nl_policy[SUNRPC_A_UNIX_GID_EXPIRY + 1]; int sunrpc_nl_ip_map_get_reqs_dumpit(struct sk_buff *skb, struct netlink_callback *cb); int sunrpc_nl_ip_map_set_reqs_doit(struct sk_buff *skb, struct genl_info *info); +int sunrpc_nl_unix_gid_get_reqs_dumpit(struct sk_buff *skb, + struct netlink_callback *cb); +int sunrpc_nl_unix_gid_set_reqs_doit(struct sk_buff *skb, + struct genl_info *info); enum { SUNRPC_NLGRP_NONE, diff --git a/net/sunrpc/svcauth_unix.c b/net/sunrpc/svcauth_unix.c index b09b911c532a4..7703523d42461 100644 --- a/net/sunrpc/svcauth_unix.c +++ b/net/sunrpc/svcauth_unix.c @@ -585,12 +585,246 @@ static int unix_gid_show(struct seq_file *m, return 0; } +static int unix_gid_notify(struct cache_detail *cd, struct cache_head *h) +{ + return sunrpc_cache_notify(cd, h, SUNRPC_CACHE_TYPE_UNIX_GID); +} + +/** + * sunrpc_nl_unix_gid_get_reqs_dumpit - dump pending unix_gid requests + * @skb: reply buffer + * @cb: netlink metadata and command arguments + * + * Walk the unix_gid cache's pending request list and create a netlink + * message with a nested entry for each cache_request, containing the + * seqno and uid. + * + * Uses cb->args[0] as a seqno cursor for dump continuation across + * multiple netlink messages. + * + * Returns the size of the reply or a negative errno. + */ +int sunrpc_nl_unix_gid_get_reqs_dumpit(struct sk_buff *skb, + struct netlink_callback *cb) +{ + struct sunrpc_net *sn; + struct cache_detail *cd; + struct cache_head **items; + u64 *seqnos; + int cnt, i, emitted; + void *hdr; + int ret; + + sn = net_generic(sock_net(skb->sk), sunrpc_net_id); + + cd = sn->unix_gid_cache; + if (!cd) + return -ENODEV; + + cnt = sunrpc_cache_requests_count(cd); + if (!cnt) + return 0; + + items = kcalloc(cnt, sizeof(*items), GFP_KERNEL); + seqnos = kcalloc(cnt, sizeof(*seqnos), GFP_KERNEL); + if (!items || !seqnos) { + ret = -ENOMEM; + goto out_alloc; + } + + cnt = sunrpc_cache_requests_snapshot(cd, items, seqnos, cnt, + cb->args[0]); + if (!cnt) { + ret = 0; + goto out_alloc; + } + + hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, &sunrpc_nl_family, + NLM_F_MULTI, SUNRPC_CMD_UNIX_GID_GET_REQS); + if (!hdr) { + ret = -ENOBUFS; + goto out_put; + } + + emitted = 0; + for (i = 0; i < cnt; i++) { + struct unix_gid *ug; + struct nlattr *nest; + + ug = container_of(items[i], struct unix_gid, h); + + nest = nla_nest_start(skb, + SUNRPC_A_UNIX_GID_REQS_REQUESTS); + if (!nest) + break; + + if (nla_put_u64_64bit(skb, SUNRPC_A_UNIX_GID_SEQNO, + seqnos[i], 0) || + nla_put_u32(skb, SUNRPC_A_UNIX_GID_UID, + from_kuid(&init_user_ns, ug->uid))) { + nla_nest_cancel(skb, nest); + break; + } + + nla_nest_end(skb, nest); + cb->args[0] = seqnos[i]; + emitted++; + } + + if (!emitted) { + genlmsg_cancel(skb, hdr); + ret = -EMSGSIZE; + goto out_put; + } + + genlmsg_end(skb, hdr); + ret = skb->len; +out_put: + for (i = 0; i < cnt; i++) + cache_put(items[i], cd); +out_alloc: + kfree(seqnos); + kfree(items); + return ret; +} + +/** + * sunrpc_nl_parse_one_unix_gid - parse one unix_gid entry from netlink + * @cd: cache_detail for the unix_gid cache + * @attr: nested attribute containing unix_gid fields + * + * Parses one unix_gid entry from a netlink message and updates the + * cache. Mirrors the logic in unix_gid_parse(). + * + * Returns 0 on success or a negative errno. + */ +static int sunrpc_nl_parse_one_unix_gid(struct cache_detail *cd, + struct nlattr *attr) +{ + struct nlattr *tb[SUNRPC_A_UNIX_GID_EXPIRY + 1]; + struct unix_gid ug, *ugp; + struct timespec64 boot; + struct nlattr *gid_attr; + int err, rem, gids = 0; + kuid_t uid; + + err = nla_parse_nested(tb, SUNRPC_A_UNIX_GID_EXPIRY, attr, + sunrpc_unix_gid_nl_policy, NULL); + if (err) + return err; + + /* uid (required) */ + if (!tb[SUNRPC_A_UNIX_GID_UID]) + return -EINVAL; + uid = make_kuid(current_user_ns(), + nla_get_u32(tb[SUNRPC_A_UNIX_GID_UID])); + ug.uid = uid; + + /* expiry (required, wallclock seconds) */ + if (!tb[SUNRPC_A_UNIX_GID_EXPIRY]) + return -EINVAL; + getboottime64(&boot); + ug.h.flags = 0; + ug.h.expiry_time = nla_get_u64(tb[SUNRPC_A_UNIX_GID_EXPIRY]) - + boot.tv_sec; + + if (tb[SUNRPC_A_UNIX_GID_NEGATIVE]) { + ug.gi = groups_alloc(0); + if (!ug.gi) + return -ENOMEM; + } else { + /* Count gids */ + nla_for_each_nested_type(gid_attr, SUNRPC_A_UNIX_GID_GIDS, + attr, rem) + gids++; + + if (gids > 8192) + return -EINVAL; + + ug.gi = groups_alloc(gids); + if (!ug.gi) + return -ENOMEM; + + gids = 0; + nla_for_each_nested_type(gid_attr, SUNRPC_A_UNIX_GID_GIDS, + attr, rem) { + kgid_t kgid; + + kgid = make_kgid(current_user_ns(), + nla_get_u32(gid_attr)); + if (!gid_valid(kgid)) { + err = -EINVAL; + goto out; + } + ug.gi->gid[gids++] = kgid; + } + groups_sort(ug.gi); + } + + ugp = unix_gid_lookup(cd, uid); + if (ugp) { + struct cache_head *ch; + + ch = sunrpc_cache_update(cd, &ug.h, &ugp->h, + unix_gid_hash(uid)); + if (!ch) { + err = -ENOMEM; + } else { + err = 0; + cache_put(ch, cd); + } + } else { + err = -ENOMEM; + } +out: + if (ug.gi) + put_group_info(ug.gi); + return err; +} + +/** + * sunrpc_nl_unix_gid_set_reqs_doit - respond to unix_gid requests + * @skb: reply buffer + * @info: netlink metadata and command arguments + * + * Parse one or more unix_gid cache responses from userspace and + * update the unix_gid cache accordingly. + * + * Returns 0 on success or a negative errno. + */ +int sunrpc_nl_unix_gid_set_reqs_doit(struct sk_buff *skb, + struct genl_info *info) +{ + struct sunrpc_net *sn; + struct cache_detail *cd; + const struct nlattr *attr; + int rem, ret = 0; + + sn = net_generic(genl_info_net(info), sunrpc_net_id); + + cd = sn->unix_gid_cache; + if (!cd) + return -ENODEV; + + nlmsg_for_each_attr_type(attr, SUNRPC_A_UNIX_GID_REQS_REQUESTS, + info->nlhdr, GENL_HDRLEN, rem) { + ret = sunrpc_nl_parse_one_unix_gid(cd, + (struct nlattr *)attr); + if (ret) + break; + } + + return ret; +} + static const struct cache_detail unix_gid_cache_template = { .owner = THIS_MODULE, .hash_size = GID_HASHMAX, .name = "auth.unix.gid", .cache_put = unix_gid_put, .cache_upcall = unix_gid_upcall, + .cache_notify = unix_gid_notify, .cache_request = unix_gid_request, .cache_parse = unix_gid_parse, .cache_show = unix_gid_show, |
