Linux filesystem development
 help / color / mirror / Atom feed
From: Sasha Levin <sashal@kernel.org>
To: patches@lists.linux.dev, stable@vger.kernel.org
Cc: Christian Brauner <brauner@kernel.org>, Li Chen <me@linux.beauty>,
	Jan Kara <jack@suse.cz>, Sasha Levin <sashal@kernel.org>,
	viro@zeniv.linux.org.uk, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org
Subject: [PATCH AUTOSEL 6.18] pidfs: preserve thread pidfds reopened by file handle
Date: Mon, 31 Aug 2026 09:27:53 -0400	[thread overview]
Message-ID: <20260831133314.4125787-445-sashal@kernel.org> (raw)
In-Reply-To: <20260831133314.4125787-1-sashal@kernel.org>

From: Christian Brauner <brauner@kernel.org>

[ Upstream commit bb6bc13c53e211d9148ed2eab3e689c5cd5c75da ]

PIDFD_THREAD shares O_EXCL. do_dentry_open() clears O_EXCL after
pidfs_export_open() validates the flags, so open_by_handle_at()
silently turns a thread pidfd into a process pidfd.

Restore PIDFD_THREAD on the opened file, matching pidfs_alloc_file().

Signed-off-by: Li Chen <me@linux.beauty>
Reviewed-by: Jan Kara <jack@suse.cz>
Link: https://patch.msgid.link/20260716052726.1032092-1-me@linux.beauty
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

## Phase 1: Commit Message Forensics

### Step 1.1: Subject Line
**Record:** `[pidfs]` `[preserve]` — Preserve thread pidfd semantics
when a pidfd is reopened via file handle (`open_by_handle_at()`).

### Step 1.2: Tags
**Record:**
- **Reviewed-by:** Jan Kara `<jack@suse.cz>`
- **Link:**
  https://patch.msgid.link/20260716052726.1032092-1-me@linux.beauty
- **Signed-off-by:** Li Chen `<me@linux.beauty>` (author)
- **Signed-off-by:** Christian Brauner `<brauner@kernel.org>` (pidfs
  maintainer)
- No **Fixes:**, **Reported-by:**, **Tested-by:**, **Cc: stable**, or
  syzbot tags
- Notable: maintainer review and ack from Brauner; no user/fuzzer
  reports

### Step 1.3: Body Analysis
**Record:**
- **Bug:** `PIDFD_THREAD` is aliased to `O_EXCL`. `do_dentry_open()`
  clears `O_EXCL` after `pidfs_export_open()` validates flags, so
  `open_by_handle_at()` drops the thread-pidfd marker.
- **Symptom:** A reopened thread pidfd silently behaves as a process
  pidfd.
- **Root cause:** `pidfs_alloc_file()` already re-applies `PIDFD_THREAD`
  after `dentry_open()`; `pidfs_export_open()` did not.
- **Version info:** None in the message.

### Step 1.4: Hidden Bug Fix?
**Record:** Yes. Despite “preserve” wording, this is a functional
correctness bug fix, not cleanup. It restores API semantics on the
`open_by_handle_at()` path.

---

## Phase 2: Diff Analysis

### Step 2.1: Inventory
**Record:**
- **Files:** `fs/pidfs.c` only (+6 / -1 net)
- **Function:** `pidfs_export_open()`
- **Scope:** Single-file, surgical fix

### Step 2.2: Code Flow Change
**Record:**
- **Before:** `pidfs_export_open()` called `dentry_open()` and returned
  immediately; `do_dentry_open()` stripped `O_EXCL` (`PIDFD_THREAD`).
- **After:** Save `file` from `dentry_open()`, then if successful
  restore `file->f_flags |= oflags & PIDFD_THREAD`.
- **Path affected:** `open_by_handle_at()` → `do_handle_open()` →
  `pidfs_export_open()` for pidfs-backed handles with `O_EXCL`.

### Step 2.3: Bug Mechanism
**Record:** **Logic / correctness fix.** `PIDFD_THREAD` is carried in
`f_flags`, not in the inode. Both thread and process pidfds share the
same `struct pid` in `inode->i_private`; semantics depend on `f_flags`.
Losing `PIDFD_THREAD` changes behavior of consumers that inspect
`f_flags`.

### Step 2.4: Fix Quality
**Record:** Obviously correct. Mirrors the existing pattern in
`pidfs_alloc_file()` in the same file. Minimal regression risk; no new
locks, APIs, or behavior changes beyond restoring intended semantics.

---

## Phase 3: Git History Investigation

### Step 3.1: Blame
**Record:** `pidfs_export_open()` introduced in `5d324e5159d9e`
(2025-11-28) without `PIDFD_THREAD` restoration. `pidfs_alloc_file()` in
the same commit already had the restoration at lines 1063–1065. Bug
present since pidfs export support landed in this tree.

### Step 3.2: Fixes: Tag
**Record:** Not applicable — no `Fixes:` tag.

### Step 3.3: Related File History
**Record:** Recent `fs/pidfs.c` commits in this tree:
- `7446125afb6d9` — pidfs: return -EREMOTE for cross-ns `PIDFD_GET_INFO`
- `ae7a542dbab5b` — pidfs: add missing `BUILD_BUG_ON()`
Standalone fix; not part of a multi-patch series.

### Step 3.4: Author Context
**Record:** Li Chen is not the primary pidfs maintainer; Christian
Brauner is. Patch reviewed by Jan Kara and merged with Brauner’s SOB.

### Step 3.5: Dependencies
**Record:** No prerequisites. Uses existing `PIDFD_THREAD`,
`dentry_open()`, and `pidfs_export_open()` infrastructure already
present in this tree.

---

## Phase 4: Mailing List and External Research

### Step 4.1: Original Discussion
**Record:** Could not retrieve lore/patch.msgid.link — Anubis bot
protection blocked WebFetch. `b4 dig -c <commit>` unavailable because
the fix commit is not in this checkout. Phase 4 partially blocked.

### Step 4.2: Reviewers
**Record:** Unverified via `b4 dig -w`. Commit message shows Reviewed-by
Jan Kara and SOB from Christian Brauner.

### Step 4.3: Bug Report
**Record:** No external bug report, syzbot link, or user Reported-by.

### Step 4.4: Related Patches
**Record:** No series indicated. Complements existing
`pidfs_alloc_file()` logic.

### Step 4.5: Stable List History
**Record:** Unverified — lore blocked. This tree already carries other
pidfs stable backports (`7446125afb6d9`, `ae7a542dbab5b`).

---

## Phase 5: Code Semantic Analysis

### Step 5.1: Key Functions
**Record:** `pidfs_export_open()`, `pidfs_alloc_file()`,
`do_dentry_open()`, `sys_open_by_handle_at()`, `sys_pidfd_send_signal()`

### Step 5.2: Callers
**Record:**
- `pidfs_export_open()` called from `do_handle_open()` in `fs/fhandle.c`
  when `eops->open` is set
- Reachable from `open_by_handle_at()` syscall (userspace)

### Step 5.3: Callees
**Record:** `dentry_open()` → `do_dentry_open()`, which clears `O_EXCL`
at `fs/open.c:981`

### Step 5.4: Reachability
**Record:** Userspace can trigger via:
1. `pidfd_open(tid, PIDFD_THREAD)`
2. `name_to_handle_at(pidfd, ...)`
3. `open_by_handle_at(mountfd, fh, O_EXCL)`
4. `pidfd_send_signal()` or other operations reading `f_flags`

### Step 5.5: Similar Patterns
**Record:** Identical restoration already exists in
`pidfs_alloc_file()`:

```1062:1065:fs/pidfs.c
        pidfd_file = dentry_open(&path, flags, current_cred());
        /* Raise PIDFD_THREAD explicitly as do_dentry_open() strips it.
*/
        if (!IS_ERR(pidfd_file))
                pidfd_file->f_flags |= (flags & PIDFD_THREAD);
```

`pidfs_export_open()` currently lacks this:

```855:862:fs/pidfs.c
static struct file *pidfs_export_open(const struct path *path, unsigned
int oflags)
{
        /*
  - Clear O_LARGEFILE as open_by_handle_at() forces it and raise
  - O_RDWR as pidfds always are.
         */
        oflags &= ~O_LARGEFILE;
        return dentry_open(path, oflags | O_RDWR, current_cred());
}
```

---

## Phase 6: Cross-Reference Against Local Tree

### Step 6.1: Buggy Code Exists?
**Record:** Yes. Local tree is **v6.18.44** (`git describe HEAD`, `make
kernelversion`). `fs/pidfs.c` exists with `pidfs_export_open()` missing
the fix. The fix commit is not present (scoped `-S 'do_dentry_open()
strips O_EXCL' -- fs/pidfs.c` returns nothing).

### Step 6.2: Backport Complications
**Record:** Clean apply expected — 6-line change in one function, no
structural conflicts visible.

### Step 6.3: Related Fixes Already Present?
**Record:** `pidfs_alloc_file()` already has the `PIDFD_THREAD`
restoration pattern. Other pidfs stable fixes (`7446125afb6d9`,
`ae7a542dbab5b`) are present. This specific export-path fix is not.

---

## Phase 7: Subsystem Context

### Step 7.1: Subsystem / Criticality
**Record:** `fs/pidfs.c` — VFS/pidfd subsystem. **IMPORTANT**: affects
process management APIs reachable from userspace; not universal like mm,
but core process-control infrastructure in modern kernels.

### Step 7.2: Activity
**Record:** Actively maintained — multiple pidfs commits in this 6.18.y
tree.

---

## Phase 8: Impact and Risk Assessment

### Step 8.1: Who Is Affected
**Record:** Users of thread pidfds (`PIDFD_THREAD`) combined with pidfs
file-handle APIs (`name_to_handle_at` / `open_by_handle_at`). Config-
independent when pidfs is present (always initialized from
`init/main.c`).

### Step 8.2: Trigger Conditions
**Record:** Reopen a thread-pidfd file handle with `O_EXCL` via
`open_by_handle_at()`. Uncommon but valid documented API usage
(`VALID_FILE_HANDLE_OPEN_FLAGS` explicitly allows `O_EXCL`).
Unprivileged users can trigger on their own pidfds.

### Step 8.3: Failure Mode Severity
**Record:** **MEDIUM–HIGH functional correctness bug.** Without
`PIDFD_THREAD`, `pidfd_send_signal()` uses `PIDTYPE_TGID` instead of
`PIDTYPE_PID`:

```4111:4115:kernel/signal.c
                /* Infer scope from the type of pidfd. */
                if (fd_file(f)->f_flags & PIDFD_THREAD)
                        type = PIDTYPE_PID;
                else
                        type = PIDTYPE_TGID;
```

Signal may be delivered to the thread group instead of the specific
thread. Not a kernel oops, but wrong-target signal delivery is a
meaningful user-visible failure. `pidfd_get_pid()` also propagates
incorrect `f_flags` to callers.

### Step 8.4: Risk-Benefit
**Record:**
- **Benefit:** Restores correct thread-vs-process pidfd semantics on
  file-handle reopen; prevents wrong signal scope.
- **Risk:** Very low — mirrors proven in-tree pattern.
- **Ratio:** Favorable for backport.

---

## Phase 9: Final Synthesis

### Step 9.1: Evidence Summary

**FOR backport:**
- Real, verifiable bug in this tree
- Wrong signal-delivery scope possible
- Tiny, obviously correct fix matching existing code
- Maintainer-reviewed (Brauner SOB, Kara Reviewed-by)
- pidfs fixes already being backported to 6.18.y
- Clean apply to local tree

**AGAINST backport:**
- Niche API path (file handles + thread pidfds)
- No syzbot/user reports
- Not a crash/corruption/deadlock
- Mailing-list discussion unverified

**Unresolved:** Full lore review thread unavailable.

### Step 9.2: Stable Rules Checklist
1. Obviously correct and tested? **PASS** — mirrors
   `pidfs_alloc_file()`; no runtime test in commit, but pattern is
   proven in-tree.
2. Fixes a real bug affecting users? **PASS** — silent semantic
   corruption of thread pidfds.
3. Important issue? **PASS (moderate)** — wrong signal target; not
   crash-level, but meaningful API correctness failure.
4. Small and contained? **PASS** — ~6 lines, one function.
5. No new features or APIs? **PASS**
6. Can apply to local tree? **PASS** — buggy code present, fix not yet
   applied.

### Step 9.3: Exception Categories
**Record:** None (not device ID, quirk, DT, build, or docs).

### Step 9.4: Decision Rationale

This is an oversight in pidfs export support: the `pidfd_open()` path
was fixed at introduction, but the `open_by_handle_at()` path was not.
For 6.18.y users adopting pidfs file handles, a reopened thread pidfd
silently becomes a process pidfd, which can cause `pidfd_send_signal()`
to target the wrong scope. The fix is minimal, matches existing in-tree
code, and carries very low regression risk. Other pidfs fixes (including
less severe ones) are already in this 6.18.44 tree, establishing
precedent for pidfs corrections in stable.

---

## Verification

- [Phase 1] Parsed subject, tags, and body from provided commit message
- [Phase 1] Confirmed no syzbot/Fixes:/Reported-by tags
- [Phase 2] Read current `pidfs_export_open()` and `pidfs_alloc_file()`
  in `fs/pidfs.c`
- [Phase 2] Verified `do_dentry_open()` strips `O_EXCL` at
  `fs/open.c:981`
- [Phase 2] Confirmed `PIDFD_THREAD` is `O_EXCL` in
  `include/uapi/linux/pidfd.h`
- [Phase 3] `git describe HEAD` → `v6.18.44-2-g1b9e1abadee04`; `make
  kernelversion` → `6.18.44`
- [Phase 3] `git blame -L 855,863 fs/pidfs.c` → `pidfs_export_open()`
  from `5d324e5159d9e`
- [Phase 3] `git blame -L 1060,1066 fs/pidfs.c` → `PIDFD_THREAD` restore
  in `pidfs_alloc_file()` from same commit
- [Phase 3] `git log --oneline -S 'do_dentry_open() strips O_EXCL' --
  fs/pidfs.c` → empty (fix not in tree)
- [Phase 3] `git show 7446125afb6d9`, `git show ae7a542dbab5b` → other
  pidfs stable backports present
- [Phase 4] WebFetch lore/patch.msgid.link → blocked by Anubis
- [Phase 4] `b4 dig` → could not run without commit hash in tree
- [Phase 5] Traced `open_by_handle_at` → `do_handle_open()` →
  `eops->open` in `fs/fhandle.c:436-437`
- [Phase 5] Verified `pidfd_send_signal()` checks `f_flags &
  PIDFD_THREAD` in `kernel/signal.c:4112-4115`
- [Phase 5] Verified `pidfd_pid()` returns same `inode->i_private`
  regardless of thread flag
- [Phase 6] Confirmed buggy `pidfs_export_open()` present in local
  6.18.44 tree
- [Phase 6] Confirmed `pidfs_init()` called unconditionally from
  `init/main.c`
- [Phase 8] Assessed failure mode as wrong signal scope, severity
  MEDIUM-HIGH functional

**YES**The background git history checks finished and match what the
analysis already used:

- **`pidfs_export_open` and `pidfs_export_operations`** both date to
  `5d324e5159d9e` in this 6.18.44 tree — the same commit that brought in
  `fs/pidfs.c`, including the existing `PIDFD_THREAD` restore in
  `pidfs_alloc_file()`.
- The broad `git log -S` search for the fix comment was killed after
  hanging; a scoped search on `fs/pidfs.c` already showed the fix is
  **not** in this tree.

**Verdict stands: YES** — small, correct fix for a real bug where
`open_by_handle_at()` drops thread-pidfd semantics on the export path.

 fs/pidfs.c | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/fs/pidfs.c b/fs/pidfs.c
index 34987fcdd9a87..30450f7f5ec5b 100644
--- a/fs/pidfs.c
+++ b/fs/pidfs.c
@@ -854,12 +854,18 @@ static int pidfs_export_permission(struct handle_to_path_ctx *ctx,
 
 static struct file *pidfs_export_open(const struct path *path, unsigned int oflags)
 {
+	struct file *file;
+
 	/*
 	 * Clear O_LARGEFILE as open_by_handle_at() forces it and raise
 	 * O_RDWR as pidfds always are.
 	 */
 	oflags &= ~O_LARGEFILE;
-	return dentry_open(path, oflags | O_RDWR, current_cred());
+	file = dentry_open(path, oflags | O_RDWR, current_cred());
+	/* do_dentry_open() strips O_EXCL, which encodes PIDFD_THREAD. */
+	if (!IS_ERR(file))
+		file->f_flags |= oflags & PIDFD_THREAD;
+	return file;
 }
 
 static const struct export_operations pidfs_export_operations = {
-- 
2.53.0


  parent reply	other threads:[~2026-08-31 13:47 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <20260831133314.4125787-1-sashal@kernel.org>
2026-08-31 13:20 ` [PATCH AUTOSEL 6.18-5.10] hfs: rework hfsplus_readdir() logic Sasha Levin
2026-08-31 13:20 ` [PATCH AUTOSEL 6.18-6.12] virtio-fs: avoid double-free on failed queue setup Sasha Levin
2026-08-31 13:21 ` [PATCH AUTOSEL 6.18-5.10] affs: handle set_blocksize failures Sasha Levin
2026-08-31 13:25 ` [PATCH AUTOSEL 6.18] netfs: Fix DIO write retry for filesystems without a ->prepare_write() Sasha Levin
2026-08-31 13:25 ` [PATCH AUTOSEL 6.18-5.10] isofs: handle set_blocksize failures Sasha Levin
2026-08-31 13:26 ` [PATCH AUTOSEL 6.18-5.10] hfsplus: rework hfsplus_readdir() logic Sasha Levin
2026-08-31 13:26 ` [PATCH AUTOSEL 6.18-6.12] netfs: Fix decision whether to disallow write-streaming due to fscache use Sasha Levin
2026-08-31 13:27 ` [PATCH AUTOSEL 6.18-6.1] cachefiles: Fix double fput Sasha Levin
2026-08-31 13:27 ` [PATCH AUTOSEL 6.18] iomap: prevent ioend merge when io_private differs Sasha Levin
2026-08-31 13:27 ` [PATCH AUTOSEL 6.18-6.12] iomap: don't make REQ_POLLED imply REQ_NOWAIT Sasha Levin
2026-08-31 13:27 ` Sasha Levin [this message]
2026-08-31 13:30 ` [PATCH AUTOSEL 6.18-5.10] hfsplus: fix issue of direct writes beyond end-of-file Sasha Levin

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=20260831133314.4125787-445-sashal@kernel.org \
    --to=sashal@kernel.org \
    --cc=brauner@kernel.org \
    --cc=jack@suse.cz \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=me@linux.beauty \
    --cc=patches@lists.linux.dev \
    --cc=stable@vger.kernel.org \
    --cc=viro@zeniv.linux.org.uk \
    /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