On 2026-10-22 01:57 +0000, Alexei Starovoitov wrote:
From: Alexei Starovoitov <ast@kernel.org>
int3_update_unoptimize() restores optimized nop10 in three writes:
48 8d 64 24 80 e8 d0 d1 d2 d3 lea -0x80(%rsp),%rsp; call tramp
cc 8d 64 24 80 e8 d0 d1 d2 d3 1. trap new entries
cc 2e 0f 1f 84 e8 d0 d1 d2 d3 2. restore bytes 1..4
66 2e 0f 1f 84 e8 d0 d1 d2 d3 3. restore byte 0
uprobe_write() can fail with -ENOMEM, or with -EINTR when the task is being
killed. When the 2nd write fails the result of the 1st one stays. Byte 0 is
a breakpoint and it works. But the next set_orig_insn() doesn't find lea in
byte 0, takes it for a plain breakpoint and restores one byte:
66 8d 64 24 80 e8 d0 d1 d2 d3 lea -0x80(%rsp),%sp; call tramp
Nothing recognizes these bytes anymore. The process keeps the call into
uprobe trampoline, which can be unmapped already.
Teach is_optimized() to recognize the half-way state, and let the 1st write
of int3_update_unoptimize() skip what is already done.
The failure wasn't reproduced, but the state can be made by hand. Attach to
nop10, call it to get it optimized, write 0xcc over byte 0, detach.
"uprobes: Don't sleep on mmap_lock under dup_mmap_sem" needs this. There
fork() removes the breakpoint from its mm. When fork() is killed in
the middle register_for_each_vma() removes it for the second time.
Fixes: 554ba38456da ("uprobes/x86: Move optimized uprobe from nop5 to nop10")
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This was a nice read.
Reviewed-by: Gregory Price (Meta) <gourry@gourry.net>
--
Gregory Price (Meta) [off-list ref]