On Fri,  6 Nov 2026 09:56:59 +0100
David Marchand <[email protected]> wrote:

> When a tap interface moves to another namespace, tap_netns_change()
> calls tap_lsc_intr_handle_set(dev, 1) to recreate the LSC interrupt
> without first tearing down the old one. The previous LSC netlink
> socket fd is never closed.
> 
> Defer the full LSC teardown and re-registration to an alarm callback
> which runs in the interrupt thread but outside any active source
> context. The alarm switches to the device's network namespace, tears
> down the old LSC handle, sets up a new one (opening a fresh netlink
> socket in the correct namespace), restores the original namespace,
> and forces carrier state back up.
> 
> Extract tap_dev_ns_switch/tap_dev_ns_restore helpers to share the
> namespace switch logic between tap_netns_change and the alarm.
> 
> Fixes: aae3a38e9e03 ("net/tap: detect namespace change")
> Cc: [email protected]
> 
> Signed-off-by: David Marchand <[email protected]>
> Reviewed-by: Robin Jarry <[email protected]>
> ---

AI review still seeing errors.

Review of [PATCH v2 0/2] net/tap: FD leak and interrupt UAF fixes

Applied to main (v26.11-rc2), both commits build with -Dwerror=true.

[PATCH v2 1/2] net/tap: fix FD leak on netns change

Error

The deferred alarm can run concurrently with the LSC teardown done by
dev_stop/dev_close and re-register the callback after the control
thread has removed it. The second rte_eal_alarm_cancel() added in v2
waits for a running alarm but does not undo what it did.

  ctrl thread (dev_stop)            intr thread
  ----------------------            -----------
  rte_eal_alarm_cancel() /* none */
                                    tap_dev_intr_handler()
                                      tap_netns_change()
                                        rte_eal_alarm_set(1, ...)
  rte_intr_callback_unregister()
    -> success (not active)
                                    tap_lsc_register_alarm()
                                      tap_lsc_intr_handle_set(dev, 0)
                                        unregister -> -ENOENT
                                        close old fd, fd = -1
                                      tap_lsc_intr_handle_set(dev, 1)
                                        new fd, callback registered
  rte_eal_alarm_cancel()
    /* spins until alarm done, returns 0 */
  tap_nl_final(new fd); fd = -1

The port is left stopped with tap_dev_intr_handler still registered on
pmd->intr_handle with fd -1, and the carrier forced up. A following
tap_dev_close() then does rte_intr_instance_free() with the callback
still registered, which is the use after free patch 2 fixes.

Repeat the teardown until the fd is stable once no alarm is pending or
running, and treat -ENOENT as already unregistered:

clean:
        for (;;) {
                rte_eal_alarm_cancel(tap_lsc_register_alarm, dev);
                fd = rte_intr_fd_get(pmd->intr_handle);
                if (fd < 0)
                        return 0;

                do {
                        ret = rte_intr_callback_unregister(pmd->intr_handle,
                                tap_dev_intr_handler, dev);
                        if (ret >= 0 || ret == -ENOENT)
                                break;
                        if (ret != -EAGAIN) {
                                TAP_LOG(ERR, "intr callback unregister failed: 
%d",
                                        ret);
                                break;
                        }
                        rte_delay_ms(100);
                } while (true);

                /* alarm may have replaced the fd while unregistering */
                rte_eal_alarm_cancel(tap_lsc_register_alarm, dev);
                if (rte_intr_fd_get(pmd->intr_handle) == fd)
                        break;
        }

        tap_nl_final(fd);
        /* fd has been closed already, only clear reference here. */
        rte_intr_fd_set(pmd->intr_handle, -1);
        return 0;

When called from the alarm itself the cancel returns with EINPROGRESS,
the fd does not change, and the loop exits on the first pass.

Review-Result: ERROR

Reply via email to