commit 5090e0bb662c53b4552b28b121ac2c2c434adb2e Author: Steve Dickson Date: Fri Sep 18 03:49:41 2026 -0400 Release 3.1.1 Signed-off-by: Steve Dickson commit bdb5dc58a322b1ec911287bb939ccff5499a7ec7 Author: Jeff Layton Date: Thu Sep 17 07:46:06 2026 -0400 nfsdctl: check the netlink attribute puts in set_listeners() nla_nest_start() only reserves a 4-byte header, so it still succeeds on a message that has no room for the 128-byte sockaddr that follows. The puts inside the nest went unchecked, so an over-full message was sent with silently truncated listener nests. The kernel then rejects it with EINVAL, which says nothing about what went wrong. MAX_NFSD_SOCKETS is 256 and nlmsg_alloc() defaults to one page, so roughly 28 listeners overflow. Signed-off-by: Jeff Layton Signed-off-by: Steve Dickson commit 458373acf75c889273ecf29da23dbd168c083524 Author: Jeff Layton Date: Thu Sep 17 07:44:25 2026 -0400 nfsdctl: reject a listener insertion when the table is full parse_listener_get() stops at MAX_NFSD_SOCKETS and publishes nfsd_socket_count = MAX_NFSD_SOCKETS. update_listeners() then scans the whole table for a match, finds none because every slot is taken, and forms &nfsd_sockets[MAX_NFSD_SOCKETS] for the insert. The memcpy() of res->ai_addr writes one element past the array. Reachable only with 256 listeners already up, and the count comes from the kernel rather than the network, so this is hardening. Signed-off-by: Jeff Layton Signed-off-by: Steve Dickson commit 6057d1412e00caa26fd4b9fbf1baa6acfea949d2 Author: Jeff Layton Date: Thu Sep 17 07:42:32 2026 -0400 nfsdctl: fix heap overflow in autostart_func() listener cleanup The listener-failure cleanup forced pools to 0 before sizing the array: pools = thread_str ? thread_str->cnt : 1; if (failed_listeners) pools = 0; threads = calloc(pools, sizeof(int)); ... if (thread_str) TAILQ_FOREACH(n, &(thread_str->fields), link) threads[idx++] = strtol(n->field, &endptr, 0); The TAILQ walk is driven by thread_str, not by pools, so it writes thread_str->cnt ints into a calloc(0) allocation. Any /etc/nfs.conf with a [nfsd] threads setting overflows the heap once a listener fails to come up. Size the array from the list and clamp pools to 0 just before threads_doit(), which is the only consumer. The netlink message is unchanged. Also switch the hash_fh_key_file() failure to goto out; it returned directly and leaked threads. Fixes: 8b02f0d5590e ("nfsdctl: cleanup listeners if some failed") Signed-off-by: Jeff Layton Signed-off-by: Steve Dickson commit 01022429804c1edb00e5e117cd1baa898004c269 Author: Jeff Layton Date: Thu Sep 17 07:38:48 2026 -0400 nfsdctl: add missing break in update_listeners() The AF_INET arm fell through into AF_INET6, so a v4 listener that matched on port and address was then re-tested as a sockaddr_in6 and skipped. -Wimplicit-fallthrough is not enabled, so nothing caught it. Terminate every arm. The AF_INET6 fallthrough was harmless, since "default:" is empty, but it is a trap for the next case added. Signed-off-by: Jeff Layton Signed-off-by: Steve Dickson commit 658dbbefd129de27a646edad23a099bfd8127ec2 Author: Jeff Layton Date: Thu Sep 17 07:37:01 2026 -0400 nfsdctl: clear rpcbind entries when the serv is torn down nfsd destroys the serv when the last thread stops. The kernel used to clear the rpcbind entries on that path, through svc_rpcb_cleanup(). A serv that let this program own rpcbind does not clear them. The entries would then outlive the server and name a port that nothing listens on. Clear them from "nfsdctl threads 0". nfs-server.service runs that command to stop the server. autostart_func() tears the serv back down the same way when a listener fails to start, so sweep there too. zero_threads was true when any one pool argument was zero. That is the wrong test to hang the sweep off: "nfsdctl threads 8 0" empties pool 1, keeps the serv, and would have cleared every entry under a running server. Sum the pool counts instead. Signed-off-by: Jeff Layton Signed-off-by: Steve Dickson commit 0ec46460fefeb6baad0db43f9f743e328795609c Author: Jeff Layton Date: Thu Sep 17 07:34:15 2026 -0400 nfsdctl: register nfsd listeners with rpcbind The kernel registered NFS and NFSACL under nfsd_mutex. Every call was synchronous. A local rpcbind that never replied therefore blocked every other NFSD netlink command, and tripped the hung-task watchdog. A kernel that offers NFSD_A_SERVER_SOCK_USERSPACE_RPCBIND makes no rpcbind call of its own. It answers a listener_set that carries the flag with the programs to register and the listeners that started. This program now does three steps instead: 1: send the flag 2: read the reply 3: register the listeners, where nothing holds a lock Read the maximum attribute of LISTENER_SET to detect support. This adds no configuration. An older kernel does not offer the attribute, so that kernel keeps the registration. A build without TI-RPC has no rpcb_set(), so it reports no support either and leaves the kernel in charge. nfsdctl clears every nfsd entry before it registers. rpcbind matches an unset on [program, version, netid] and takes no address, so an unset cannot drop one of two TCP ports. The sweep also replaces the svc_unregister() that svc_rpcb_setup() ran at serv creation. An sv_no_rpcbind serv no longer reaches that call. That sweep makes step 2 load-bearing, so register only once the reply has been parsed. A reply that carries no rpcbind attributes would otherwise clear every NFS and NFSACL entry and put nothing back, leaving a running server that advertises nothing. Abort if the flag does not fit in the message. Sending without it leaves the kernel owning rpcbind while this program rewrites the same entries. The reply names only the listeners that started. A request that failed part way therefore registers what exists, not what the caller asked for. Signed-off-by: Jeff Layton Signed-off-by: Steve Dickson commit 66c8313b36604594643ade8eb8436d04198ae3b2 Author: Jeff Layton Date: Thu Sep 17 07:31:31 2026 -0400 nfsdctl: add rpcbind registration helpers Add the code that calls the local rpcbind. The callers come next. nfsd_rpcb_register() matches each listener against each program that the kernel reported. It calls rpcb_set() for every pair. nfsd_rpcb_unset_all() clears every nfsd entry with nfs_svc_unregister(). That helper calls rpcb_unset() with no netconfig, so one call covers every netid. Three rules match what the kernel did: - nfsdctl registers the wildcard address with the listener's port. It does not register the address that the listener uses. __svc_rpcb_register4() and __svc_rpcb_register6() did the same. - nfsdctl does not register an RDMA listener. svc_register() runs only from svc_setup_socket(), so an RDMA listener never reached rpcbind. - nfsdctl does not register a program that carries the no-udp flag on udp or udp6. NFSv4 sets vs_need_cong_ctrl, so the kernel never advertised NFSv4 there. A failed call writes a warning and does not stop the caller. NFSv4 does not need rpcbind. A v2 or v3 client that already knows the port does not need it either. rpcb_set() and the netconfig database are TI-RPC only, so the registration side sits under HAVE_LIBTIRPC with a warning stub, matching getport.c and rpc_socket.c. nfsd_rpcb_unset_all() needs neither and stays unconditional; nfs_svc_unregister() has a pmap_unset() variant. struct server_socket moves to the new header, because both files read it. The netlink spec generates nfsdctl.h, so the definition cannot live there. Signed-off-by: Jeff Layton Signed-off-by: Steve Dickson commit 3c86ed7fda845f15740c75da7f0f925ac835e1b9 Author: Jeff Layton Date: Thu Sep 17 07:28:08 2026 -0400 nfsdctl: share the listener nest parser parse_listener_get() unpacks a NFSD_A_SERVER_SOCK_ADDR nest inline. It also treats every top-level attribute as one of those nests. A LISTENER_GET reply carries nothing else, so the loop is correct today. A reply that mixes attributes would count the other attributes as listeners. Move the nest walk into parse_sock_nest(). Skip an attribute that is not a listener. Stop at MAX_NFSD_SOCKETS. No functional change. Signed-off-by: Jeff Layton Signed-off-by: Steve Dickson commit 5ec977693002ff418a59946ab04ff5acd050e85a Author: Jeff Layton Date: Thu Sep 17 07:26:06 2026 -0400 nfsdctl: probe the policy of any command query_nfsd_nl_policy() asks CTRL_CMD_GETPOLICY about NFSD_CMD_THREADS_SET only. getpolicy_handler() writes the highest attribute straight into a global. A second command cannot use either function. query_nfsd_nl_cmd_policy() now takes the command number. getpolicy_handler() now writes through its callback argument. query_nfsd_nl_policy() keeps its name and still probes THREADS_SET. No functional change. Signed-off-by: Jeff Layton Signed-off-by: Steve Dickson commit 89ee66d5d619f4be60adcefa2557fdbdcf2f59c9 Author: Jeff Layton Date: Thu Sep 17 07:24:30 2026 -0400 nfsdctl: sync nfsd_netlink.h with the kernel Import the attributes that let a caller own rpcbind registration: - NFSD_A_SERVER_SOCK_USERSPACE_RPCBIND on the request - NFSD_A_SERVER_SOCK_RPCBIND, and the NFSD_A_RPCBIND_* nest, on the reply The file comes from include/uapi/linux/nfsd_netlink.h. No code reads the new attributes yet. Bump the USE_SYSTEM_NFSD_NETLINK_H probe to one of the new attributes. It tested NFSD_CMD_SERVER_STATS_GET, which has been in the uapi header since v6.9, so any host with headers that new picks the system copy and loses everything imported here. Signed-off-by: Jeff Layton Signed-off-by: Steve Dickson commit c02bba0d009a7918f9353ed6e97e5757b120bc88 Author: Jeff Layton Date: Thu Sep 17 02:48:03 2026 -0400 mountd: don't warn about pipefs submounts nobody asked to export nfsd_export() warns whenever dump_to_cache() cannot work out the attributes for a path, including a crossmnt submount the admin never asked to export. With "/" exported crossmnt that is one warning per proc, sys or autofs mount per TTL, and nothing the admin can act on. Use the same test the netlink downcall now uses: warn only when the request is for a path exported in its own right. Tested-by: Mantas Mikulėnas Assisted-by: LLM Signed-off-by: Jeff Layton Signed-off-by: Steve Dickson commit 8b824b651f061be58ade9d5e5c4b134e0f856d4c Author: Jeff Layton Date: Thu Sep 17 02:45:52 2026 -0400 mountd: bound the retry queues A request is deferred when we cannot yet say whether its path or fsid is exportable. lookup_fsid() defers on "!found && dev_missing", and dev_missing counts any export whose "mountpoint" is not mounted, whatever fsid was asked for. The fsid comes out of the filehandle the client sent, so on a server with one unmounted mountpoint= export - the case this retry logic exists for - every fsid a client invents gets a queue entry. None of the three queues has a limit. The pre-existing "delayed" queue nfsd_fh() feeds is the worst: it strndup()s the whole upcall message and does not deduplicate at all, so even a repeated fsid allocates again. delayed_export and delayed_expkey dedup, but still grow without end, and the dedup walk makes each insertion O(n). Cap all three at MAX_DELAYED and drop the new request once full. Queuing is only an optimisation: the kernel repeats the upcall when the client retries, so a dropped deferral costs latency, not correctness - the same trade the existing allocation-failure paths already make. The cap applies only where a record is created. nfsd_retry_fh(), nl_retry_export() and nl_retry_expkey() re-queue a record they already dequeued and own, and must not be refused. nfsd_fh() had no dedup walk, so it gets a counting one; it ends at the tail the append needs anyway. Tested-by: Mantas Mikulėnas Signed-off-by: Jeff Layton Assisted-by: LLM Signed-off-by: Steve Dickson commit 5775e86190d27165b42f72097eaf52186845c1f1 Author: Jeff Layton Date: Thu Sep 17 02:42:36 2026 -0400 mountd: drop a deferred fsid lookup once it has been answered Building an expkey batch can defer an entry, and a later failure of that batch sends the same entries through nl_expkey_singly(). An entry that resolves the second time is answered, but its record stays on delayed_expkey, so nl_retry_expkey() answers it again within RETRY_SEC. Take it off the queue instead, as nl_export_singly() already does for delayed_export. The tuple test nl_delay_expkey() dedups on moves into delayed_expkey_matches() so both users agree on it. Tested-by: Mantas Mikulėnas Signed-off-by: Jeff Layton Assisted-by: LLM Signed-off-by: Steve Dickson commit 19b06272e2575fd4b6fb55a15afb8c5bee628c48 Author: Jeff Layton Date: Thu Sep 17 02:39:17 2026 -0400 mountd: retry export attributes that fail to resolve for a passing reason export_attrs_build() answers -1 both for a filesystem that can never be exported and for one it could not measure this time: - nfsd_path_statfs() on a re-exported NFS mount can give ETIMEDOUT with "softerr", or EIO with plain "soft" - fsidnum_get_by_path() returns the same false whether fsidd is restarting, answered with nonsense, or has no fsid for the path Both then set errno to EINVAL, so both downcalls deny the path for default_ttl. An fsidd restart takes a working re-export offline until the negative entry expires. Report EAGAIN for the cases we cannot decide and keep EINVAL for the rest. The netlink path turns EAGAIN into EXPORT_RETRY, and nfsd_export() leaves the request for the next upcall, which is what both already do when is_mountpoint() fails oddly. statfs errors are sorted with path_lookup_error(), the same test is_mountpoint() callers use, plus two that are decidable even though it does not name them: - ENOSYS: a filesystem with no statfs will never answer - 0: a chrooted worker thread ran the stat and its errno never reached us, which is how the is_mountpoint() callers already read it Tested-by: Mantas Mikulėnas Signed-off-by: Jeff Layton Assisted-by: LLM Signed-off-by: Steve Dickson commit fdd064507af1cbab9f6ef0803699b93b8fa295c8 Author: Jeff Layton Date: Thu Sep 17 02:36:27 2026 -0400 mountd: give each worker its own netlink command socket cache_open() opens the nfsd and sunrpc command sockets before cache_fork_workers() forks, so every worker shares one fd, and a copy-on-write struct nl_sock carrying the same s_seq_next/s_seq_expect. Only the notify sockets get nl_socket_disable_seq_check(); the command sockets keep libnl's default sequence check, which each worker will happily pass on the other's reply. Two workers in a command/reply round trip can therefore take each other's ack or NLMSG_ERROR. cache_nl_set_reqs() then reports the wrong outcome and the caller answers the wrong client and path - marking it exported, retryable, or negatively cached. Reopen both command sockets in the worker child. genl_connect() binds a fresh port id, so each worker gets its own reply stream. The notify sockets stay shared: they are receive-only, have the sequence check disabled, and are nonblocking, so whichever worker gets there first takes the notification. Tested-by: Mantas Mikulėnas Signed-off-by: Jeff Layton Assisted-by: LLM Signed-off-by: Steve Dickson commit 5329d389337183d1d5705fd37ae6b9fce0cd9f12 Author: Jeff Layton Date: Thu Sep 17 02:32:10 2026 -0400 mountd: bound the junction path before copying it into e_path create_junction_exportent() strcpy()s the junction pathname into exportent.e_path, a fixed char[NFS_MAXPATHLEN+1]. Neither downcall bounds the path first: - nfsd_export() sizes it from the 32KiB pipefs channel buffer - the netlink path strdup()s it out of NFSD_A_SVC_EXPORT_PATH A junction more than NFS_MAXPATHLEN bytes deep therefore corrupts the heap. Reaching it needs a trusted.junction.nfs xattr, so only server root can set one up, but the copy should not depend on that. Reject the path instead, as mkexportent() already does. The callers handle a NULL exportent: both downcalls answer negative. Signed-off-by: Jeff Layton Tested-by: Mantas Mikulėnas Assisted-by: LLM Signed-off-by: Steve Dickson commit 17427829116d0a174f5a6aa5e1795bc8bad634a5 Author: Jeff Layton Date: Thu Sep 17 02:28:50 2026 -0400 mountd: retry unresolvable fsid lookups on the netlink downcall nfsd_fh() defers a request instead of an answer in two cases: - the fsid names a device that is not present - the export has a "mountpoint" that is not mounted The filesystem can appear soon, and an answer now gives a spurious ESTALE. cache_nl_process_expkey() answered negative in both cases, so a not-yet-mounted autofs submount failed immediately. Move the fsid lookup out of nfsd_handle_fh() into lookup_fsid(). Call it from both downcalls. The netlink path then also gets: - dev_missing accounting for unmatchable and unmounted exports - reexpdb_uncover_subvolume() for re-exported fsidnums - the V4ROOT tie-break and duplicate-filehandle warning - the fsidtype range check Deferred netlink requests use their own list. cache_process() retries them on the same RETRY_SEC cadence as the pipefs ones. The kernel keeps the upcall pending until then. expkey has the same batching hole that svc_export had. nfsd_nl_expkey_set_reqs_doit() stops at the first entry that it refuses. It refuses an entry when kern_path() on the path fails. It also refuses an entry when auth_domain_find() on the client fails. The kernel never processed the entries behind the refused one. Those clients hung. Resubmit a rejected batch one entry at a time, as the svc_export path now does, tracing the fallback at D_CALL. A refused entry gets no negative fallback. The kernel builds a negative expkey entry from auth_domain_find() alone, so a negative answer would replace a path that kern_path() refused. But nfsd_nl_add_expkey() sets an expiry of 0x7fffffff. A path that disappeared for a moment would become a denial until the next "exportfs -f". Log the entry and leave it pending for the next upcall, which is what pipefs does when the channel write fails. A libnl or socket failure is a different case. The kernel never saw the message, so the request is still unanswered. cache_nl_set_reqs() reports the kernel's errno separately, and nl_expkey_one() returns such a request to the retry list. Reported-by: Mantas Mikulenas Tested-by: Mantas Mikulėnas Assisted-by: LLM Signed-off-by: Jeff Layton Signed-off-by: Steve Dickson commit f4d779b398c642a6bcf94b240d6b3d74ab692282 Author: Jeff Layton Date: Thu Sep 17 02:23:55 2026 -0400 mountd: don't leak the parent export's fsid onto crossmnt submounts nfsd_nl_add_export() sent e_flags and e_fsid straight from the parent exportent, unlike dump_to_cache() which strips NFSEXP_FSID (and picks a path-derived uuid) when the upcall path is a submount below the exported one. v4root forces "/" to fsid=0, so exporting "/" with crossmnt handed every submount fsid=0 as well. Both exports then encode the same fsid, the submount's filehandles decode back to "/", and the client sees NFS: server X error: fileid changed fsid 0:150: expected fileid 0x2, got 0x100 followed by ESTALE. Re-export via fsidnum was missing for the same reason, so a re-exported submount got the parent's fsid too. Use export_attrs_build() for the flags, fsid and uuid, mask the per- flavor secinfo flags to match (the kernel rejects the entry otherwise), and fall back to a negative entry when the export cannot be resolved. While here, honour the export's own e_ttl on positive entries. Two consequences worth noting. A crossmnt submount under an fsid= parent now gets its own filehandles, so clients holding the old (aliased) ones see ESTALE once - that aliasing was the bug. And a crossmnt submount on a filesystem with no blkid uuid and no statfs fsid (tmpfs, say) now has neither fsid= nor uuid, so check_export() refuses it; the preceding patch turns that into a negative entry instead of a failed batch, so this one depends on it. Nobody asked to export that submount, so it is only worth D_GENERAL - the warning stays for a path exported in its own right. Reported-by: Mantas Mikulenas Tested-by: Mantas Mikulėnas Assisted-by: LLM Signed-off-by: Jeff Layton Signed-off-by: Steve Dickson commit 5d56d7439e45b168141f984adf3507cb4b804e7c Author: Jeff Layton Date: Thu Sep 17 02:21:07 2026 -0400 mountd: answer requests the kernel rejects on the netlink downcall The kernel refuses an svc_export it cannot build a filehandle for - a 9p submount picked up by crossmnt, say - and it fails the whole SET_REQS message when it does. cache_nl_process_export() batches every pending request into one message and never looks at the result, so: - every entry queued behind the bad one is dropped - nothing downgrades the bad path to a negative entry - the request stays pending and the client hangs on the lookup Pending requests then pile up on each notification: cache_nl_process_export: 5 pending export requests cache_nl_set_reqs: SET_REQS failed: -7 nfsd_export() has no such hole: a rejected dump_to_cache() write returns -1 and it answers negative instead. Keep the batch for the fast path, but resubmit it one entry at a time when the kernel rejects it, and answer negative for whichever entries it still refuses. nfsd_nl_svc_export_set_reqs_doit() commits each entry as it parses it and stops at the first failure, so the resubmit re-sends some entries the kernel already took; that is harmless, as the update is idempotent, and it is the only way to find the one that failed. nl_add_export_req() no longer flushes the message itself; the caller owns the batch so it knows what to resubmit. Only fall back to a negative entry when the kernel actually answered. libnl folds its own errors into the same NLE_* space as the kernel's, so cache_nl_set_reqs() now reports the kernel's errno separately: a broken socket must not cache "not exported" for default_ttl. A negative entry the kernel already refused - the path or the client's auth_domain went away - is not resent as another negative either. An entry too large for a message of its own is a third case. mountd never sent it, so the kernel never refused it. Log the size failure and answer negative, rather than blame the filesystem for it. The resubmit makes a refusal routine, and the kernel tends to notify one request at a time, so warning about it printed the same failure twice per bad path. Trace it at D_NETLINK instead, with nl_geterror() and the kernel's errno rather than a raw number: the -7 above is NLE_INVAL, libnl's own code for the kernel's EINVAL, not an errno. Callers that have no fallback keep their warning through the new cache_nl_flush_reqs(), which ip_map and unix_gid now use, and the -ENOMEM in cache_nl_set_reqs() becomes -NLE_NOMEM so the value is in the space nl_geterror() reads. A path nobody asked to export - /proc or /sys below a crossmnt "/" - is expected to be refused and the admin cannot act on it, so the negative fallback only warns for a path exported in its own right. Reported-by: Mantas Mikulenas Tested-by: Mantas Mikulėnas Assisted-by: LLM Signed-off-by: Jeff Layton Signed-off-by: Steve Dickson commit d0a8e08fe86d2db7c139ef6bee42738bd606c602 Author: Jeff Layton Date: Thu Sep 17 02:18:07 2026 -0400 mountd: handle unmountable paths and junctions in the netlink downcall Two things nfsd_export() does that cache_nl_process_export() did not: - If is_mountpoint() fails with an error that isn't a plain lookup failure, we can't tell whether the export is available. Defer the request instead of answering "not exported". - When no export matches, check for a junction before denying, so referrals still work. Only when the client resolved, as nfsd_export() does: client_check() dereferences the addrinfo for wildcard and netgroup clients. Split lookup_nonexport() so both downcalls share the junction lookup, and factor the per-request work into nl_add_export_req() so the deferred path can reuse it. Deferred requests go on their own list, deduped on (client, path) and retried from cache_process() every RETRY_SEC. The kernel keeps the upcall pending in the meantime. Assisted-by: LLM Tested-by: Mantas Mikulėnas Signed-off-by: Jeff Layton Signed-off-by: Steve Dickson commit 1bfd99546faf0aa003ca377ee2321714001b3e3e Author: Jeff Layton Date: Thu Sep 17 02:11:49 2026 -0400 mountd: factor out the per-path export attribute computation dump_to_cache() adjusts the export flags, fsid and uuid before handing them to the kernel, because the upcall path may be a crossmnt submount rather than the exported path itself. Pull that into export_attrs_build() so the netlink downcall can use it too. No functional change. Assisted-by: LLM Tested-by: Mantas Mikulėnas Signed-off-by: Jeff Layton Signed-off-by: Steve Dickson commit 2d3ba969c5977b81a3cab6fcaa5dd0f335a831b2 Author: Scott Mayhew Date: Wed Sep 16 17:42:54 2026 -0400 gssd: clean up threads on shutdown Valgrind reports leaks such as the following: ==1781== 736 bytes in 2 blocks are possibly lost in loss record 831 of 926 ==1781== at 0x487F36B: calloc (vg_replace_malloc.c:1616) ==1781== by 0x403A75F: calloc (rtld-malloc.h:44) ==1781== by 0x403A75F: allocate_dtv (dl-tls.c:477) ==1781== by 0x403B2E5: _dl_allocate_tls (dl-tls.c:734) ==1781== by 0x4B6579E: pthread_create@@GLIBC_2.34 (in /usr/lib64/libc.so.6) ==1781== by 0x400E531: start_upcall_thread.constprop.0 (gssd_proc.c:971) ==1781== by 0x400EC34: handle_gssd_upcall (gssd_proc.c:1117) ==1781== by 0x48B9D9B: event_persist_closure (event.c:1623) ==1781== by 0x48B9D9B: event_process_active_single_queue (event.c:1682) ==1781== by 0x48BA5BE: event_process_active (event.c:1783) ==1781== by 0x48BA5BE: event_base_loop (event.c:2006) ==1781== by 0x400A431: main (gssd.c:1292) ==1781== This is due to the thread-local storage allocated by pthread_create() as well as the upcall_thread_info struct allocated by start_upcall_thread(). Those do eventually get freed by the watchdog thread in scan_active_thread_list(). The thread-local storage gets freed when pthread_tryjoin_np() succeeds and the upcall_thread_info gets freed via TAILQ_REMOVE() + free(). But since the watchdog thread sleeps between iterations (30 seconds by default), from the perspective of tools like valgrind there?s a leak. Add logic to clean up the threads on shutdown to make tools like valgrind happy. Signed-off-by: Scott Mayhew Signed-off-by: Steve Dickson commit 909fa03de1a64d94c6b66938c74b49aea606ec1c Author: Scott Mayhew Date: Wed Sep 16 17:41:00 2026 -0400 gssd: fix upcall_* leak when re-reading service info gssd_scan_clnt() calls gssd_read_service_info() whenever clp->prog == 0, and the function's fail path leaves clp->prog == 0. If an initial read fails (e.g. the "info" file is not fully populated when inotify fires), the fail path strdup()s the parsed values into clp->upcall_address, upcall_port, upcall_protoname and upcall_service. A subsequent re-scan then calls gssd_read_service_info() again and overwrites those fields without freeing them first: the success path sets them to NULL and the fail path strdup()s fresh copies, leaking the previous allocations. Free the upcall_* fields at the top of the function before repopulating. clp is calloc()'d so the fields are NULL on the first call, making the free() calls safe. This fixes the following leak: ==1300== 5 bytes in 1 blocks are definitely lost in loss record 13 of 929 ==1300== at 0x4877826: malloc (vg_replace_malloc.c:447) ==1300== by 0x4B7B9FE: strdup (strdup.c:42) ==1300== by 0x400D08D: gssd_read_service_info (gssd.c:373) ==1300== by 0x400D08D: gssd_scan_clnt (gssd.c:734) ==1300== by 0x400D7C6: gssd_inotify_clnt (gssd.c:922) ==1300== by 0x400D7C6: gssd_inotify_cb (gssd.c:988) ==1300== by 0x48B9D9B: event_persist_closure (event.c:1623) ==1300== by 0x48B9D9B: event_process_active_single_queue (event.c:1682) ==1300== by 0x48BA5BE: event_process_active (event.c:1783) ==1300== by 0x48BA5BE: event_base_loop (event.c:2006) ==1300== by 0x400A431: main (gssd.c:1292) ==1300== Assisted-by: Claude Opus 4.8 Signed-off-by: Scott Mayhew Signed-off-by: Steve Dickson commit 4c99234a3b993a1cb511f2af033382f1d3e300d2 Author: Olga Kornievskaia Date: Wed Sep 16 06:48:43 2026 -0400 fsidd: require root credentials on abstract socket fsidd listens on an abstract UNIX domain socket which has no filesystem-level access control. Any local unprivileged user can connect and issue mutating commands (get_or_create_fsidnum) that grow the reexport database without limit, degrading mountd performance. Add a SO_PEERCRED check to reject connections from non-root clients. The only legitimate client is mountd, which runs as root. Fixes: 6fd2732d ("export: Add fsidd") Signed-off-by: Olga Kornievskaia Signed-off-by: Steve Dickson commit 32d2a1581911b418f3bdf0537a19236730df6816 Author: Scott Mayhew Date: Wed Sep 16 06:41:16 2026 -0400 fsidd: replace assertions with explicit malformed-input handling fsidd relies on assert() to ensure that an argument is present after the command prefix while parsing commands received over the local socket. Malformed input can cause fsidd to abort. Replace the assertions with explicit malformed-input handling and return an error to the client. Signed-off-by: Scott Mayhew Signed-off-by: Steve Dickson commit 73cebb0d987cdaa8266b7db6fc6ca21bc59d1555 Author: Scott Mayhew Date: Wed Sep 16 06:38:13 2026 -0400 statd: fix notify_list struct leak in nlist_free nlist_free() freed an entry's string members but never the notify_list struct itself, despite its name and comment ("Destroy an entry ... and free the memory"). Every caller was expected to free the struct separately, but several did not: - process_reply() and process_notify_list() (rmtcall.c) clean up entries cloned onto the notify list during SM_NOTIFY handling. - sm_unmon_1_svc() and sm_unmon_all_1_svc() (monitor.c) leak the same way on the SM_UNMON paths. Fix the root cause by having nlist_free() free the struct, and drop the now-redundant free() calls in sm_mon_1_svc() and load_one_host() that would otherwise double-free. nlist_kill() previously called nlist_free(head, *head) followed by free(*head); since nlist_remove() (called from nlist_free) already advanced *head to the next entry, that free(*head) freed the wrong, still-live element. Pass NULL so nlist_free() only frees the entry and advance the list manually. Assisted-by: Claude Opus 4.8 Signed-off-by: Scott Mayhew Signed-off-by: Steve Dickson commit 971db88b063cfb505e14ec1f22fb36e65d75d1ed Author: Scott Mayhew Date: Wed Sep 16 06:35:35 2026 -0400 fsidd: handle accept4() failures before creating client event Handle failures from accept4() before creating a client event, and add a short backoff on errors indicating resource exhaustion. Signed-off-by: Scott Mayhew Signed-off-by: Steve Dickson commit 0afdfcba623473b5c8e3edbc0e5b233ed691da27 Author: Scott Mayhew Date: Wed Sep 16 06:33:38 2026 -0400 junction: fix use-after-free in locations_to_fslocdata() While parsing a multi-location junction, locations_to_fslocdata() sets last_path = root_path and then frees root_path, leaving last_path dangling. On the next iteration, strcmp(rootpath, last_path) dereferences freed memory. Fix it by transferring ownership of root_path to last_path when they should be aliased. Signed-off-by: Scott Mayhew Signed-off-by: Steve Dickson commit 5860374349e3a6a88f6af740c08a7b050f22cad2 Author: Scott Mayhew Date: Wed Sep 16 06:31:36 2026 -0400 junction: fix buffer over-read in junction_parse_xml_read() junction_get_xattr() returns an opaque buffer, but junction_parse_xml_read() treats it as a string by passing it to xlog(). Fix it by allocating an extra byte and null-terminating the buffer in junction_get_xattr(). Signed-off-by: Scott Mayhew Signed-off-by: Steve Dickson commit 843579c5131e47daf94c1284ecd8f95eae308998 Author: Scott Mayhew Date: Wed Sep 16 06:29:40 2026 -0400 blkmapd: reject zero-length signature components while decoding block signatures A zero-length signature can cause blkmapd to map to an unintended device. Signed-off-by: Scott Mayhew Signed-off-by: Steve Dickson commit e21df117adf370876a0e7707893395382e6bb974 Author: Prabhakar Pujeri Date: Mon Sep 14 00:57:03 2026 -0400 statd: document the NLM port options in usage() rpc.statd accepts -T/--nlm-port and -U/--nlm-udp-port and both are described in rpc.statd(8), but usage() never mentions them. Signed-off-by: Prabhakar Pujeri Signed-off-by: Steve Dickson commit 3085bd607512c688c4c673090545ad543ab61cdd Author: Prabhakar Pujeri Date: Mon Sep 14 00:55:01 2026 -0400 gssd: sync usage() and man page synopsis with actual options The getopt string no longer contains 'l', so the '-l' advertised by usage() and the rpc.gssd(8) synopsis only earns users an "invalid option". Conversely, the -T rpc-timeout option exists but was never added to usage(). Drop the stale -l from both places and document -T. Signed-off-by: Prabhakar Pujeri Signed-off-by: Steve Dickson commit 94af8796c6475894a5a30635fb429c851bef513d Author: Prabhakar Pujeri Date: Mon Sep 14 00:50:48 2026 -0400 gssd: reject server hostnames that do not fit find_keytab_entry() copies a hostname from the kernel upcall into a fixed NI_MAXHOST buffer with strcpy(), allowing an overlong name to overwrite the stack. Use snprintf() and check its result. Reject a truncated hostname with ENAMETOOLONG instead of performing a keytab lookup with a different name. The verified string also fits in the following myhostad copy. Signed-off-by: Prabhakar Pujeri Signed-off-by: Steve Dickson commit 38d28de44666225a57b86ca0aca84266dcc6c24c Author: Prabhakar Pujeri Date: Mon Sep 14 00:48:08 2026 -0400 idmapd: validate NAMETOID names within their fixed-size field imconv() limits len to IDMAP_NAMESZ - 1 and then reads im_name[len + 1]. An unterminated name makes this read beyond im_name, while a terminated name can be rejected based on the unrelated byte after its NUL character. Use strnlen() to require a terminator within im_name. Mark an unterminated request as invalid before returning it to the kernel. Signed-off-by: Prabhakar Pujeri Signed-off-by: Steve Dickson commit 45a549cd4f9ce5cb831a92ef75dc50a0dce4c555 Author: Prabhakar Pujeri Date: Mon Sep 14 00:45:48 2026 -0400 gssd: fix use of uninitialized memory in error message gssd_get_topdir() prints tdi->name in the inotify_add_watch() error path, but that flexible array member is only filled in by the strcpy() after the call. The message ends up dumping uninitialized heap memory and can even read past the allocation if no NUL byte is nearby. Print the name argument passed in by the caller instead; it is exactly the directory the watch was being added for. Signed-off-by: Prabhakar Pujeri Signed-off-by: Steve Dickson commit 630393e9c73667b3fda6a7507cb5171ec0ec38dc Author: Petr Vorel Date: Sat Aug 29 17:14:13 2026 -0400 getport: Fix switch case on old compilers Buildroot distro compiles with fairly old compilers, which defaults to old C11 standard (some of still used compilers don't even have newer standard than C11). Workaround code which requires C23 with using { }. This fixes error: getport.c:460:3: error: a label can only be part of a statement and a declaration is not a statement size_t path_len = len - offsetof(struct sockaddr_un, sun_path); ^~~~~~ Fixes: 1a6dcd74 ("Removed warnings in nfs_sockaddr2universal()") Signed-off-by: Petr Vorel Signed-off-by: Steve Dickson commit 95f26f452a7953ce53abc538fb97e1a5ebbf8b24 Author: Olga Kornievskaia Date: Sat Aug 29 17:25:00 2026 -0400 mount: fix snprintf return value handling in error formatting snprintf() returns the number of bytes that would have been written if sufficient space were available, not the number actually written. When the formatted string exceeds PATH_MAX (e.g. due to a long progname), the return value exceeds the buffer size. Using that unclamped value as an index into errbuf produces an out-of-bounds pointer, and subsequent snprintf() calls write past the buffer. Add errbuf_clamp() to bound the position to the valid range before it is used as a buffer index. Apply it in rpc_mount_errors(), sys_mount_errors(), and rpc_strerror(). Fixes: 6e54f6179cb9 ("text-based mount.nfs: Add text-based error reporting function") Signed-off-by: Olga Kornievskaia Signed-off-by: Steve Dickson commit d005ca6218e9c149e2484dc5694607e92340b528 Author: Salvatore Bonaccorso Date: Sat Aug 29 17:18:33 2026 -0400 rpcctl: SyntaxWarning: 'return' in a 'finally' block Trent W. Buck reported that when using rpcctl with Python 3.14 version, a SyntaxWarning warning is issued on the use of return in the finally block in the read_info_file function. Replace the finally clause with an except BaseException, and dedent the return by one level up. Reported-by: "Trent W. Buck" Link: https://docs.python.org/3/whatsnew/3.14.html#pep-765-control-flow-in-finally-blocks Closes: https://bugs.debian.org/1144982 Signed-off-by: Salvatore Bonaccorso Signed-off-by: Steve Dickson commit 67ed1bdb1af1c70a5fd3b377a997401b7676b827 Author: jaipaul Cheernam Date: Sat Aug 15 14:20:03 2026 -0400 exportfs: link failure with --disable-nfsdctl Building nfs-utils 2.9.2 with --disable-nfsdctl fails to link exportfs. This is a regression from 2.9.1. Signed-off-by: Steve Dickson commit cec8eeb69dc65eb91e0cb23ebff23530d9397425 Author: Jaipaul Cheernam Date: Sat Aug 15 14:00:36 2026 -0400 getport: fix missing stddef.h inclusion getport.c uses offsetof() but does not include . On glibc this works because offsetof is pulled in transitively, but on musl it is not available without the explicit include, causing: getport.c:459:41: error: implicit declaration of function 'offsetof' Signed-off-by: Jaipaul Cheernam Signed-off-by: Steve Dickson commit cf80edaeb9b120b7a533e0d3aea2f05cfcab85ae Author: xuchenchen Date: Sat Aug 15 13:58:16 2026 -0400 statd: fix memory leak in sm_mon_1_svc() when existing host re-monitors When an already-monitored host sends a new SM_MON request with a different priv cookie, the code sets existing=1 and reuses the existing notify_list entry. However, clnt->dns_name is overwritten with the newly allocated dnsname without freeing the old value, causing a memory leak. Since statd is a long-running daemon, repeated SM_MON requests with changing cookies will accumulate leaked strings. Fix by freeing the old dns_name before overwriting it. Signed-off-by: xuchenchen Signed-off-by: Steve Dickson