From: Yu Zhang <yuz08559@gmail.com>
To: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Mike Christie <michael.christie@oracle.com>,
"Martin K . Petersen" <martin.petersen@oracle.com>,
linux-usb@vger.kernel.org, target-devel@vger.kernel.org,
linux-scsi@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [BUG] usb: gadget: f_tcm: NULL-ptr-deref / use-after-free when the UDC is disconnected during in-flight target I/O
Date: Tue, 20 Oct 2026 15:26:29 +1000 [thread overview]
Message-ID: <20261020052629.92551-1-yuz08559@gmail.com> (raw)
Hi,
f_tcm frees its per-endpoint USB requests on the teardown path without
waiting for in-flight target_core commands to finish, so a command that
is still being processed by the backend when the gadget is disconnected
completes into freed/NULL request memory.
The teardown path (host disconnect / port reset, "echo '' > UDC", or
set_config 0) reaches the function's ->disable:
tcm_disable() f_tcm.c:2562
tcm_cleanup_old_alt() f_tcm.c:2366
uasp_cleanup_old_alt() [UAS] f_tcm.c:538
bot_cleanup_old_alt() [BOT] f_tcm.c:365
bot_cleanup_old_alt() / uasp_cleanup_old_alt() disable the endpoints and
then free the request objects and NULL the pointers, e.g. (BOT):
usb_ep_free_request(fu->ep_in, fu->bot_req_in);
...
fu->bot_req_in = NULL; f_tcm.c:383
(the UAS path NULLs stream->req_in/req_out/req_status the same way, and
tcm_unbind()/tcm_free() later free descriptors and the f_uas itself).
There is no target_wait_for_sess_cmds() / command drain anywhere on the
disable/unbind path -- the only thing cancelled is the delayed_set_alt
work.
The SCSI session (I_T nexus) is owned by the configfs TPG, not by the
gadget function, so unbinding the UDC does not stop or drain the
in-flight se_cmds: they keep running in target_core and complete
asynchronously. The completion reaches the fabric queue_data_in callback
with no check of endpoint/teardown state:
target_complete_ok_work()
se_tfo->queue_data_in == usbg_send_read_response() f_tcm.c:1175
bot_send_read_response() [BOT] f_tcm.c:209
fu->bot_req_in->buf = cmd->data_buf; f_tcm.c:232
uasp_send_read_response()/uasp_prepare_r_request() [UAS] f_tcm.c:574
fu->bot_req_in (resp. stream->req_in) has just been freed and set to
NULL by the concurrent teardown, so the write faults. The queue_status
path (usbg_send_status_response) touches the freed req_status in the
same way.
KASAN, reproduced:
BUG: KASAN: null-ptr-deref in usbg_send_read_response+0x377/0x560
Write of size 8 at addr 0000000000000038 by task kworker/1:0/23
Workqueue: target_completion target_complete_ok_work
Call Trace:
usbg_send_read_response+0x377/0x560
target_complete_ok_work+0x499/0x850
process_one_work+0x46c/0x970
worker_thread+0x395/0x6e0
kthread+0x211/0x270
ret_from_fork+0x333/0x590
ret_from_fork_asm+0x1a/0x30
(offset 0x38 is req->buf: fu->bot_req_in == NULL after bot_cleanup_old_alt()).
If the teardown has also reached tcm_free(), cmd->fu is dangling and this
is a use-after-free of the f_uas instead of a NULL deref.
== Reproduction ==
f_tcm target on dummy_hcd, KASAN. dummy_hcd defaults to HighSpeed, so the
function runs in BOT mode and the host side binds usb-storage.
- backstore: brd /dev/ram0 behind dm-delay with a 300ms read delay, so
a READ stays in-flight in the backend; iblock on the dm-delay device.
- target: /sys/kernel/config/target/usb_gadget/naa.<wwn>/tpgt_1 with a
LUN linked to the backstore and a nexus, enabled.
- gadget: usb_gadget/g1 with functions/tcm.0 linked into a config,
bound to the dummy UDC; the host enumerates /dev/sda.
Then:
- task A: continuous O_DIRECT reads of the host /dev/sda (each becomes a
real READ(10) to the gadget, parked 300ms in the backend).
- task B: echo "" > g1/UDC (disconnect while a READ is in-flight)
The in-flight READ completes on the target_completion workqueue after the
teardown freed fu->bot_req_in, and bot_send_read_response() writes through
it. The dm-delay read delay only makes the window deterministic; on real
hardware the window is the backing-store latency, and a plain cable
unplug / host port reset during I/O takes the same ->disable path.
The disable/unbind path frees the endpoint request objects but nothing
waits for the target commands that still reference them to drain.
next reply other threads:[~2026-10-20 5:26 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-20 5:26 Yu Zhang [this message]
2026-10-20 6:04 ` [BUG] usb: gadget: f_tcm: NULL-ptr-deref / use-after-free when the UDC is disconnected during in-flight target I/O Greg Kroah-Hartman
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=20261020052629.92551-1-yuz08559@gmail.com \
--to=yuz08559@gmail.com \
--cc=gregkh@linuxfoundation.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-scsi@vger.kernel.org \
--cc=linux-usb@vger.kernel.org \
--cc=martin.petersen@oracle.com \
--cc=michael.christie@oracle.com \
--cc=target-devel@vger.kernel.org \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.