io_uring MSG_RING SEND_FD skips file_receive LSM hook

JUNYI LIU <[email protected]>
Newsgroups org.kernel.vger.io-uring
Message-ID <CADxpCqA4jsObAAgJRcSk2jh-X-VSyVQguk3hAV4_ntO8R5XrQw@mail.gmail.com>
Hello,

I found that io_uring IORING_MSG_SEND_FD can install a registered file from
a source ring into a target ring fixed-file table without invoking the
security_file_receive() LSM hook used by classic fd receive paths.

I reproduced this in a disposable kernel lab with a BPF-LSM file_receive
deny policy: SCM_RIGHTS receipt was blocked and incremented the
file_receive counter, while MSG_RING SEND_FD installed the same file for a
lower-privileged receiver and did not increment the counter.

The attached plain-text report includes the affected path, tested versions,
observed result, claim boundary, and suggested fix direction. A tested
reproducer is available if you would like me to send it.

This report was prepared with AI assistance.

Regards,
JUNYI LIU
report.txt (text/plain, 7.1 KB)
Subject: io_uring MSG_RING SEND_FD skips security_file_receive() for target fixed-file installation

Reporter: JUNYI LIU

Summary

IORING_MSG_SEND_FD can transfer a registered file from a source io_uring into
a target io_uring fixed-file table without invoking security_file_receive().
Classic fd receive paths such as SCM_RIGHTS call receive_fd(), and receive_fd()
calls security_file_receive(file) before publishing the received file. Current
MSG_RING SEND_FD instead calls __io_fixed_fd_install() directly on the target
ring fixed-file table.

This creates an LSM mediation gap for applications that use io_uring MSG_RING
SEND_FD for the same fd-passing use cases as SCM_RIGHTS. In a live local lab,
a BPF-LSM file_receive deny policy blocked classic SCM_RIGHTS receipt for a
lower-privileged receiver, but the same receiver could read the protected file
after MSG_RING SEND_FD installed it into the receiver's target ring fixed-file
slot.

Affected code path

Tested source commit:
6916d5703ddf9a38f1f6c2cc793381a24ee914c6

Runtime reproduction kernel:
Linux 6.1.0-48-cloud-amd64, Debian 6.1.172-1

The issue appears to be present from the introduction of fixed-file passing:
e6130eba8a848a7a6ba6c534bd8f6d60749ae1a9
io_uring: add support for passing fixed file descriptors

Relevant current source behavior:

- fs/file.c receive_fd() calls security_file_receive(file).
- fs/file.c receive_fd_replace() also calls security_file_receive(file).
- io_uring/openclose.c io_install_fixed_fd() calls receive_fd().
- io_uring/msg_ring.c io_msg_install_complete() calls
  __io_fixed_fd_install(target_ctx, src_file, msg->dst_fd) directly.
- io_uring/msg_ring.c io_msg_send_fd() reaches io_msg_install_complete()
  either directly or through target task work, depending on target ring flags.

Security invariant

The missing hook matters because the kernel already treats receiving an open
file as a security-relevant operation:

- security_file_receive() is documented as checking whether a process may
  receive an open file descriptor via IPC.
- receive_fd() and receive_fd_replace() both call that hook before installing
  received files.
- The upstream fixed-fd-install hardening commit
  16bae3e1377846734ec6b87eee459c0f3551692c says the credentials used for
  security_file_receive() are security relevant when an io_uring-private file
  descriptor is made accessible to a userspace task.
- The original IORING_MSG_SEND_FD commit describes the feature as supporting
  classic SCM_RIGHTS use cases.

Observed behavior

The reproducer used a disposable QEMU guest with a BPF-LSM program attached to
lsm/file_receive. The BPF-LSM program denied every file_receive operation and
incremented a counter whenever the hook ran.

The live run used a lower-privileged receiver with uid/gid 65534. The receiver
could not open the root-owned 0600 secret path directly. Classic SCM_RIGHTS
send from the higher-privileged process completed on the sender side, but the
receiver did not obtain a usable fd under the file_receive deny policy. The
BPF counter incremented after that SCM_RIGHTS receive attempt.

In the same run, IORING_MSG_SEND_FD successfully installed the same open file
into the receiver's target ring fixed-file table. The receiver then read the
secret through IOSQE_FIXED_FILE. The BPF counter did not increment during the
MSG_RING SEND_FD transfer.

Key output from the live run:

BPF_LSM_FILE_RECEIVE_DENY_ATTACHED
COUNTER_AFTER_ATTACH file_receive_count=0
CROSS_UID_SCM_RIGHTS_SEND_RESULT=1 errno=0
receiver_uid=65534 receiver_open_secret_errno=13
CROSS_UID_SCM_RIGHTS_RECV_RESULT fd=-2 errno=2
COUNTER_AFTER_SCM_RIGHTS file_receive_count=1
CROSS_UID_MSG_RING_SEND_FD_RESULT=0
CROSS_UID_TARGET_MSG_RING_NOTIFY_RESULT=0
CROSS_UID_TARGET_FIXED_READ_RESULT=27 data=PR032_CROSS_UID_LSM_SECRET
PR032_CROSS_UID_BPF_LSM_MSG_RING_BYPASS_ORACLE
COUNTER_AFTER_MSG_RING file_receive_count=1

Interpretation

SCM_RIGHTS and MSG_RING SEND_FD were exercised in the same guest under the same
file_receive deny policy.

The SCM_RIGHTS control shows that the LSM hook was active and that the
receiver did not obtain a usable received fd.

The MSG_RING SEND_FD path shows that the target fixed-file installation did
not invoke file_receive and still gave the lower-privileged receiver usable
open-file authority.

Security impact

This is not a memory corruption bug and I am not claiming an arbitrary root
shell or generic local privilege escalation.

The security impact is a receive-side LSM mediation bypass for io_uring
fixed-file passing. If an application or broker uses IORING_MSG_SEND_FD as an
SCM_RIGHTS-style fd-passing mechanism, the receiving task can obtain usable
open-file authority through a target io_uring fixed slot even when the active
LSM policy would deny classic fd receipt through security_file_receive().

The lab used BPF-LSM because it provides a narrow, deterministic live oracle.
I have not replayed a full SELinux, AppArmor, or Smack policy profile, so I am
not claiming policy-specific impact for those LSMs beyond the shared missing
file_receive hook.

Suggested fix direction

The fix should make IORING_MSG_SEND_FD enforce the same receive-side security
decision as other fd receive paths before installing the file into the target
ring fixed-file table.

The main subtlety is credential context. For target rings that use deferred
task work, the check should run with the intended receiver task context. For
direct installation paths, the kernel likely needs either a receiver-context
rule, a forced target-task installation path when available, or rejection of
cross-task SEND_FD cases where the correct receiver credential context cannot
be identified.

A regression test should include:

- an LSM file_receive deny policy with a visible counter;
- a classic SCM_RIGHTS control that triggers the hook and denies receipt;
- an IORING_MSG_SEND_FD attempt into a target fixed-file slot;
- verification that the target cannot consume the fixed slot after the fix.

Reproducer status

A tested reproducer is available. I did not attach it in this first message
because the report was prepared with AI assistance and the initial contact is
being kept to a short plain-text email plus a non-runnable technical report.
I can send the reproducer if you would like to review it.

Prior-art check

I checked local upstream history and public search terms around:

- IORING_MSG_SEND_FD
- MSG_RING SEND_FD
- security_file_receive
- file_receive
- IORING_OP_FIXED_FD_INSTALL

I found nearby history for IORING_MSG_SEND_FD and for the 2024
IORING_OP_FIXED_FD_INSTALL hardening, but I did not find an exact public fix
or report for MSG_RING SEND_FD skipping security_file_receive().

Claim boundary

Proven:

- a live BPF-LSM lsm/file_receive deny policy was attached;
- classic SCM_RIGHTS hit the file_receive hook and did not give the receiver a
  usable fd;
- MSG_RING SEND_FD did not hit the hook;
- the lower-privileged receiver read the protected file via the target ring
  fixed-file slot after MSG_RING SEND_FD.

Not claimed:

- memory corruption;
- arbitrary root shell;
- generic sandbox or container escape;
- SELinux/AppArmor/Smack policy-specific exploitability without a concrete
  policy replay;
- exploitability where a higher-trust sender never uses MSG_RING SEND_FD.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.