From: Jeff Layton <jlayton@kernel.org>
To: "Steve Dickson" <steved@redhat.com>,
"Mantas Mikulėnas" <grawity@gmail.com>
Cc: Chuck Lever <cel@kernel.org>,
linux-nfs@vger.kernel.org, Jeff Layton <jlayton@kernel.org>
Subject: [PATCH nfs-utils 4/5] mountd: don't leak the parent export's fsid onto crossmnt submounts
Date: Wed, 09 Sep 2026 08:56:47 -0400 [thread overview]
Message-ID: <20260909-nl-crossmnt-v1-4-4064b5a3bd85@kernel.org> (raw)
In-Reply-To: <20260909-nl-crossmnt-v1-0-4064b5a3bd85@kernel.org>
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.
Reported-by: Mantas Mikulėnas <grawity@gmail.com>
Assisted-by: LLM
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
support/export/cache.c | 53 +++++++++++++++++++++++++++++++-------------------
1 file changed, 33 insertions(+), 20 deletions(-)
diff --git a/support/export/cache.c b/support/export/cache.c
index d118834da29a..1f81a67e35cf 100644
--- a/support/export/cache.c
+++ b/support/export/cache.c
@@ -1500,7 +1500,8 @@ static int nfsd_nl_add_fsloc(struct nl_msg *msg, struct exportent *ep)
return 0;
}
-static int nfsd_nl_add_secinfo(struct nl_msg *msg, struct exportent *ep)
+static int nfsd_nl_add_secinfo(struct nl_msg *msg, struct exportent *ep,
+ struct export_attrs *ea)
{
struct sec_entry *p;
@@ -1520,7 +1521,7 @@ static int nfsd_nl_add_secinfo(struct nl_msg *msg, struct exportent *ep)
if (nla_put_u32(msg, NFSD_A_AUTH_FLAVOR_PSEUDOFLAVOR,
p->flav->fnum) < 0 ||
nla_put_u32(msg, NFSD_A_AUTH_FLAVOR_FLAGS,
- p->flags) < 0)
+ (p->flags | ea->sec_extra) & ea->sec_mask) < 0)
return -1;
nla_nest_end(msg, sec);
}
@@ -1545,15 +1546,26 @@ static int nfsd_nl_add_xprtsec(struct nl_msg *msg, struct exportent *ep)
return 0;
}
+/*
+ * Add one svc_export response. @ea must be the attributes computed by
+ * export_attrs_build() for (@path, @exp), and is ignored when @exp is NULL.
+ * The only failure mode is a full message, so the caller can retry with a
+ * fresh one.
+ */
static int nfsd_nl_add_export(struct nl_msg *msg, char *domain, char *path,
- struct exportent *exp, int ttl)
+ struct exportent *exp, struct export_attrs *ea,
+ int ttl)
{
struct nlattr *nest;
time_t now = time(0);
- char u[16];
+ uint64_t expiry;
+ /* A positive entry carries the export's own ttl */
+ if (exp)
+ ttl = (int)exp->e_ttl;
if (ttl <= 1)
ttl = default_ttl;
+ expiry = now + ttl;
nest = nla_nest_start(msg, NFSD_A_SVC_EXPORT_REQS_REQUESTS);
if (!nest)
@@ -1561,7 +1573,7 @@ static int nfsd_nl_add_export(struct nl_msg *msg, char *domain, char *path,
if (nla_put_string(msg, NFSD_A_SVC_EXPORT_CLIENT, domain) < 0 ||
nla_put_string(msg, NFSD_A_SVC_EXPORT_PATH, path) < 0 ||
- nla_put_u64(msg, NFSD_A_SVC_EXPORT_EXPIRY, now + ttl) < 0)
+ nla_put_u64(msg, NFSD_A_SVC_EXPORT_EXPIRY, expiry) < 0)
goto nla_failure;
if (!exp) {
@@ -1573,26 +1585,19 @@ static int nfsd_nl_add_export(struct nl_msg *msg, char *domain, char *path,
nla_put_u32(msg, NFSD_A_SVC_EXPORT_ANON_GID,
exp->e_anongid) < 0 ||
nla_put_u32(msg, NFSD_A_SVC_EXPORT_FLAGS,
- exp->e_flags) < 0 ||
+ ea->flags) < 0 ||
nla_put_s32(msg, NFSD_A_SVC_EXPORT_FSID,
- exp->e_fsid) < 0)
+ ea->fsidnum) < 0)
goto nla_failure;
if (nfsd_nl_add_fsloc(msg, exp))
goto nla_failure;
- if (exp->e_uuid) {
- get_uuid(exp->e_uuid, 16, u);
- if (nla_put(msg, NFSD_A_SVC_EXPORT_UUID,
- 16, u) < 0)
- goto nla_failure;
- } else if (uuid_by_path(path, 0, 16, u)) {
- if (nla_put(msg, NFSD_A_SVC_EXPORT_UUID,
- 16, u) < 0)
- goto nla_failure;
- }
+ if (ea->have_uuid &&
+ nla_put(msg, NFSD_A_SVC_EXPORT_UUID, 16, ea->uuid) < 0)
+ goto nla_failure;
- if (nfsd_nl_add_secinfo(msg, exp))
+ if (nfsd_nl_add_secinfo(msg, exp, ea))
goto nla_failure;
if (nfsd_nl_add_xprtsec(msg, exp))
@@ -1713,6 +1718,7 @@ static enum export_result nl_add_export_req(struct nl_msg *msg, char *dom,
nfs_export *found = NULL;
struct exportent *epp = NULL;
struct exportent *junction = NULL;
+ struct export_attrs ea = {};
enum export_result res;
int ttl = 0;
@@ -1759,7 +1765,14 @@ static enum export_result nl_add_export_req(struct nl_msg *msg, char *dom,
}
}
- if (nfsd_nl_add_export(msg, dom, path, epp, ttl) < 0)
+ if (epp && export_attrs_build(&ea, path, epp) < 0) {
+ xlog(L_WARNING, "Cannot export %s, possibly unsupported"
+ " filesystem or fsid= required", path);
+ epp = NULL;
+ ttl = 0;
+ }
+
+ if (nfsd_nl_add_export(msg, dom, path, epp, &ea, ttl) < 0)
res = EXPORT_FULL;
else
res = epp ? EXPORT_ANSWERED : EXPORT_DENIED;
@@ -1777,7 +1790,7 @@ static void nl_export_negative(char *dom, char *path)
NFSD_CMD_SVC_EXPORT_SET_REQS, 0);
if (!msg)
return;
- if (nfsd_nl_add_export(msg, dom, path, NULL, 0) == 0)
+ if (nfsd_nl_add_export(msg, dom, path, NULL, NULL, 0) == 0)
cache_nl_set_reqs(nfsd_nl_cmd_sock, msg, NULL);
nlmsg_free(msg);
}
--
2.55.0
next prev parent reply other threads:[~2026-09-09 12:56 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-09 12:56 [PATCH nfs-utils 0/5] mountd: fixes for netlink up/downcall interfaces Jeff Layton
2026-09-09 12:56 ` [PATCH nfs-utils 1/5] mountd: factor out the per-path export attribute computation Jeff Layton
2026-09-09 12:56 ` [PATCH nfs-utils 2/5] mountd: handle unmountable paths and junctions in the netlink downcall Jeff Layton
2026-09-09 12:56 ` [PATCH nfs-utils 3/5] mountd: answer requests the kernel rejects on " Jeff Layton
2026-09-09 12:56 ` Jeff Layton [this message]
2026-09-09 12:56 ` [PATCH nfs-utils 5/5] mountd: retry unresolvable fsid lookups " Jeff Layton
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260909-nl-crossmnt-v1-4-4064b5a3bd85@kernel.org \
--to=jlayton@kernel.org \
--cc=cel@kernel.org \
--cc=grawity@gmail.com \
--cc=linux-nfs@vger.kernel.org \
--cc=steved@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox