From: Sasha Levin Date: Mon, 27 Jul 2026 22:18:58 +0000 (-0400) Subject: Fixes for all trees X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=e33d88e7b5f86256866a4e1422cc31e9385270c5;p=thirdparty%2Fkernel%2Fstable-queue.git Fixes for all trees Signed-off-by: Sasha Levin --- diff --git a/queue-5.10/crypto-rsa-pkcs1pad-don-t-warn-on-an-empty-digest.patch b/queue-5.10/crypto-rsa-pkcs1pad-don-t-warn-on-an-empty-digest.patch new file mode 100644 index 0000000000..bee6297d78 --- /dev/null +++ b/queue-5.10/crypto-rsa-pkcs1pad-don-t-warn-on-an-empty-digest.patch @@ -0,0 +1,55 @@ +From 0d9ae5875a76227ceda38aab66bef549e6a0b28a Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Mon, 20 Jul 2026 21:15:33 +0200 +Subject: crypto: rsa-pkcs1pad: Don't WARN on an empty digest + +From: Doruk Tan Ozturk + +KEYCTL_PKEY_VERIFY lets an unprivileged caller supply a zero-length +digest (in_len == 0). keyctl_pkey_params_get_2() accepts the zero +length and the request reaches pkcs1pad_verify(), where the empty +digest is rejected but only after being passed through +WARN_ON(!req->dst_len). The warning is therefore directly +user-triggerable, and on kernels built with panic_on_warn=1 an +unprivileged process can panic the machine -- a local denial of +service. Reproduced as UID 65534 in a setuid sandbox. + +Keep rejecting the invalid request with -EINVAL, but do not emit a +warning for the user-controlled length. + +This is the 5.10.y/5.15.y form of the fix, where the length is read +directly from req->dst_len rather than cached in a digest_size local. +Mainline does not contain this code path; commit 1e562deacecc +("crypto: rsassa-pkcs1 - Migrate to sig_alg backend") removed +pkcs1pad_verify() in v6.13-rc1. This is a minimal fix for the +affected stable branches. The 6.1.y/6.6.y/6.12.y form +(WARN_ON(!digest_size)) is sent as a separate patch. + +Found by 0sec automated security-research tooling (https://0sec.ai). + +Fixes: c7381b012872 ("crypto: akcipher - new verify API for public key algorithms") +Cc: stable@vger.kernel.org +Assisted-by: 0sec:multi-model +Signed-off-by: Doruk Tan Ozturk +Reviewed-by: Lukas Wunner +Signed-off-by: Sasha Levin +--- + crypto/rsa-pkcs1pad.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/crypto/rsa-pkcs1pad.c b/crypto/rsa-pkcs1pad.c +index a4ebbb889274eb..1cde1e7c9ae43d 100644 +--- a/crypto/rsa-pkcs1pad.c ++++ b/crypto/rsa-pkcs1pad.c +@@ -535,7 +535,7 @@ static int pkcs1pad_verify(struct akcipher_request *req) + int err; + + if (WARN_ON(req->dst) || +- WARN_ON(!req->dst_len) || ++ !req->dst_len || + !ctx->key_size || req->src_len != ctx->key_size) + return -EINVAL; + +-- +2.53.0 + diff --git a/queue-5.10/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch b/queue-5.10/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch new file mode 100644 index 0000000000..33dbf29540 --- /dev/null +++ b/queue-5.10/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch @@ -0,0 +1,458 @@ +From aad70180e7c856978814bb3ca066df392cbfef15 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Mon, 27 Jul 2026 11:13:08 +0200 +Subject: posix-cpu-timers: Prevent UAF caused by non-leader exec() race + +From: Thomas Gleixner + +commit 920f893f735e92ba3a1cd9256899a186b161928d upstream. + +Wongi and Jungwoo decoded and reported a non-leader exec() related race +which can result in an UAF: + + sys_timer_delete() exec() + posix_cpu_timer_del() + // Observes old leader + p = pid_task(pid, pid_type); de_thread() + switch_leader(); + release_task(old_leader) + __exit_signal(old_leader) + sighand = lock(old_leader, sighand); + posix_cpu_timers*_exit(); + sighand = lock_task_sighand(p) unhash_task(old_leader); + sh = lock(p, sighand) old_leader->sighand = NULL; + unlock(sighand); + (p->sighand == NULL) + unlock(sh) + return NULL; + + // Returns without action + if(!sighand) + return 0; + free_posix_timer(); + +This is "harmless" unless the deleted timer was armed and enqueued in +p->signal because on exec() a TGID targeted timer is inherited. + +As sys_timer_delete() freed the underlying posix timer object +run_posix_cpu_timers() or any timerqueue related add/delete operations on +other timers will access the freed object's timerqueue node, which results +in an UAF. + +There is a similar problem vs. posix_cpu_timer_set(). For regular posix +timers it just transiently returns -ESRCH to user space, but for the use +case in do_cpu_nanosleep() it's the same UAF just that the k_itimer is +allocated on the stack. + +Also posix_cpu_timer_rearm() fails to rearm the timer, which means it stops +to expire. + +While debating solutions Frederic pointed out another problem: + + posix_cpu_timer_del(tmr) + __exit_signal(p) + posix_cpu_timers*_exit(p); + unhash_task(p); + p->sighand = NULL; + sh = lock_task_sighand(p) + sighand = p->sighand; + if (!sighand) + return NULL; + lock(sighand); + + if (!sh) + WARN_ON_ONCE(timer_queued(tmr)); + +On weakly ordered architectures it is not guaranteed that +posix_cpu_timer_del() will observe the stores in posix_cpu_timers*_exit() +when p->sighand is observed as NULL, which means the WARN() can be a false +positive. + +Solve these issues by: + + 1) Changing the store in __exit_signal() to smp_store_release(). + + 2) Adding a smp_acquire__after_ctrl_dep() into the !sighand path + of lock_task_sighand(). + + 3) Creating a helper function for looking up the task and locking sighand + which does not return when sighand == NULL. Instead it retries the + task lookup and only if that fails it gives up. + + 4) Using that helper in the three affected functions. + +#1/#2 ensures that the reader side which observes sighand == NULL also +observes all preceeding stores, i.e. the stores in posix_cpu_timers*_exit() +and the ones in unhash_task(). + +#3 ensures that the above described non-leader exec() situation is handled +gracefully. When the task lookup returns the old leader, but sighand == +NULL then it retries. In the non-leader exec() case the subsequent task +lookup will observe the new leader due to #1/#2. In normal exit() scenarios +the subsequent lookup fails. + +When the task lookup fails, the function also checks whether the timer is +still enqueued and issues a warning if that's the case. Unfortunately there +is nothing which can be done about it, but as the task is already not +longer visible the timer should not be accessed anymore. This check also +requires memory ordering, which is not provided when the first lookup +fails. To achieve that the check is preceeded by a smp_rmb() which pairs +with the smp_wmb() in write_seqlock() in __exit_signal(). That ensures that +the stores in posix_cpu_timers*_exit() are visible. + +The history of the non-leader exec() issue goes back to the early days of +posix CPU timers, which stored a pointer to the group leader task in the +timer. That obviously fails when a non-leader exec() switches the leader. +commit e0a70217107e ("posix-cpu-timers: workaround to suppress the problems +with mt exec") added a temporary workaround for that in 2010 which survived +about 10 years. The fix for the workaround changed the task pointer to a +pid pointer, but failed to see the subtle race described above. So the +Fixes tag picks that commit, which seems to be halfways accurate. + +Thanks to Frederic Weissbecker, Oleg Nesterov and Peter Zijlstra for +review, feedback and suggestions and to Wongi and Jungwoo for the excellent +bug report and analysis! + +Fixes: 55e8c8eb2c7b ("posix-cpu-timers: Store a reference to a pid not a task") +Reported-by: Wongi Lee +Reported-by: Jungwoo Lee +Signed-off-by: Thomas Gleixner +Reviewed-by: Oleg Nesterov +Cc: stable@vger.kernel.org +Signed-off-by: Sasha Levin +--- + kernel/exit.c | 8 +- + kernel/signal.c | 10 +- + kernel/time/posix-cpu-timers.c | 185 +++++++++++++++++++++------------ + 3 files changed, 137 insertions(+), 66 deletions(-) + +diff --git a/kernel/exit.c b/kernel/exit.c +index 99ab6d7a09cee6..675a0f4c89e19b 100644 +--- a/kernel/exit.c ++++ b/kernel/exit.c +@@ -200,7 +200,13 @@ static void __exit_signal(struct task_struct *tsk) + * doing sigqueue_free() if we have SIGQUEUE_PREALLOC signals. + */ + flush_sigqueue(&tsk->pending); +- tsk->sighand = NULL; ++ ++ /* ++ * Ensure that all preceeding state is visible. Pairs with ++ * the smp_acquire__after_ctrl_dep() in the sighand == NULL ++ * path of lock_task_sighand(). ++ */ ++ smp_store_release(&tsk->sighand, NULL); + spin_unlock(&sighand->siglock); + + __cleanup_sighand(sighand); +diff --git a/kernel/signal.c b/kernel/signal.c +index 463b798651b6a6..e0fb44e57a3166 100644 +--- a/kernel/signal.c ++++ b/kernel/signal.c +@@ -1375,8 +1375,16 @@ struct sighand_struct *__lock_task_sighand(struct task_struct *tsk, + rcu_read_lock(); + for (;;) { + sighand = rcu_dereference(tsk->sighand); +- if (unlikely(sighand == NULL)) ++ if (unlikely(sighand == NULL)) { ++ /* ++ * Pairs with the smp_store_release() in ++ * __exit_signal(). It ensures that all state ++ * modifications to the task preceeding the store are ++ * visible to the callers of lock_task_sighand(). ++ */ ++ smp_acquire__after_ctrl_dep(); + break; ++ } + + /* + * This sighand can be already freed and even reused, but +diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c +index ded4a0edaa04e9..7cf6f083a54968 100644 +--- a/kernel/time/posix-cpu-timers.c ++++ b/kernel/time/posix-cpu-timers.c +@@ -405,6 +405,110 @@ static int posix_cpu_timer_create(struct k_itimer *new_timer) + return 0; + } + ++/* ++ * Lookup the task via timer->it.cpu.pid and attempt to lock the task's sighand. ++ * ++ * This can race with the reaping of the task: ++ * ++ * CPU0 CPU1 ++ * ++ * // Finds task ++ * p = pid_task(pid, pid_type); __exit_signal(p) ++ * lock(p, sighand); ++ * posix_cpu_timers*_exit(); ++ * sighand = lock_task_sighand(p); unhash_task(p); ++ * p->sighand = NULL; ++ * unlock(sighand); ++ * ++ * In this case sighand is NULL, which means the task and the associated timer ++ * queue cannot be longer accessed safely. ++ * ++ * __exit_signal() invokes posix_cpu_timers_exit() and if the thread group is ++ * dead it also invokes posix_cpu_timers_group_exit(). These functions delete ++ * all pending timers from the related timer queues. The POSIX timers (k_itimer) ++ * themself are still accessible, but not longer connected to the task. ++ * ++ * exec() works slightly differently. The task which exec()'s terminates all ++ * other threads in the thread group and runs __exit_signal() on them. As the ++ * thread group is not dead they only clean up the per task timers via ++ * posix_cpu_timers_exit(). ++ * ++ * As the TGID on exec() stays the same per process timers stay queued, if they ++ * are armed. This works without a problem when exec() is done by the thread ++ * group leader. If a non-leader thread exec()'s this can end up in the ++ * following scenario: ++ * ++ * CPU0 CPU1 ++ * // Returns old leader ++ * p = pid_task(pid, pid_type); de_thread() ++ * switch_leader() ++ * release_task(old leader) ++ * __exit_signal() ++ * old_leader->sighand = NULL; ++ * // Returns NULL ++ * sighand = lock_task_sighand(p) ++ * ++ * That's problematic for several functions: ++ * ++ * - posix_cpu_timer_del(): If the timer is still enqueued on the task the ++ * underlying k_itimer will be freed which results in a UAF in ++ * run_posix_cpu_timers() or on timerqueue related add/delete operations. ++ * If the timer is not enqueued, the failure is harmless ++ * ++ * - posix_cpu_timer_set(): Independent of the enqueued state that results in a ++ * transient failure which is user space visible (-ESRCH) for regular posix ++ * timers. But for the use case in do_cpu_nanosleep() it's the same UAF ++ * problem just that the timer is allocated on the stack. ++ * ++ * - posix_cpu_timer_rearm(): Timer is not enqueued at that point, but this ++ * silently ignores the rearm request, which is a functional problem as the ++ * timer wont expire anymore. ++ */ ++static struct task_struct *timer_lock_sighand(struct k_itimer *timer, unsigned long *flags) ++{ ++ enum pid_type type = clock_pid_type(timer->it_clock); ++ struct cpu_timer *ctmr = &timer->it.cpu; ++ ++ guard(rcu)(); ++ ++ for (;;) { ++ struct task_struct *t = pid_task(timer->it.cpu.pid, type); ++ ++ /* Fail if the task cannot be found. */ ++ if (!t) ++ break; ++ ++ /* Try to lock the task's sighand */ ++ if (lock_task_sighand(t, flags)) ++ return t; ++ ++ /* ++ * The next PID lookup might either fail or return the new ++ * leader. This is correct for both exit() and exec(). ++ */ ++ } ++ ++ /* ++ * If the timer is still enqueued, warn. There is nothing safe to do ++ * here as there might be two timers in there which are removed in ++ * parallel and that will cause more damage than good. This should never ++ * happen! ++ * ++ * Ensure that the stores to the timer and timerqueue are visible: ++ * ++ * __exit_signal() ++ * posix_cpu_timers*_exit() ++ * write_seqlock(seqlock) ++ * smp_wmb(); <------- ++ * __unhash_process() | !pid_task() ++ * ----> smp_rmb(); ++ * WARN_ON_ONCE(...) ++ */ ++ smp_rmb(); ++ WARN_ON_ONCE(ctmr->head || timerqueue_node_queued(&ctmr->node)); ++ return NULL; ++} ++ + /* + * Clean up a CPU-clock timer that is about to be destroyed. + * This is called from timer deletion with the timer already locked. +@@ -414,28 +518,13 @@ static int posix_cpu_timer_create(struct k_itimer *new_timer) + static int posix_cpu_timer_del(struct k_itimer *timer) + { + struct cpu_timer *ctmr = &timer->it.cpu; +- struct sighand_struct *sighand; + struct task_struct *p; + unsigned long flags; + int ret = 0; + +- rcu_read_lock(); +- p = cpu_timer_task_rcu(timer); +- if (!p) +- goto out; ++ p = timer_lock_sighand(timer, &flags); + +- /* +- * Protect against sighand release/switch in exit/exec and process/ +- * thread timer list entry concurrent read/writes. +- */ +- sighand = lock_task_sighand(p, &flags); +- if (unlikely(sighand == NULL)) { +- /* +- * This raced with the reaping of the task. The exit cleanup +- * should have removed this timer from the timer queue. +- */ +- WARN_ON_ONCE(ctmr->head || timerqueue_node_queued(&ctmr->node)); +- } else { ++ if (likely(p)) { + if (timer->it.cpu.firing) + ret = TIMER_RETRY; + else +@@ -444,8 +533,6 @@ static int posix_cpu_timer_del(struct k_itimer *timer) + unlock_task_sighand(p, &flags); + } + +-out: +- rcu_read_unlock(); + if (!ret) + put_pid(ctmr->pid); + +@@ -575,21 +662,17 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, + clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock); + u64 old_expires, new_expires, old_incr, val; + struct cpu_timer *ctmr = &timer->it.cpu; +- struct sighand_struct *sighand; + struct task_struct *p; + unsigned long flags; + int ret = 0; + +- rcu_read_lock(); +- p = cpu_timer_task_rcu(timer); +- if (!p) { +- /* +- * If p has just been reaped, we can no +- * longer get any information about it at all. +- */ +- rcu_read_unlock(); ++ p = timer_lock_sighand(timer, &flags); ++ /* ++ * If p has just been reaped, we can no longer get any information about ++ * it at all. ++ */ ++ if (!p) + return -ESRCH; +- } + + /* + * Use the to_ktime conversion because that clamps the maximum +@@ -597,20 +680,6 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, + */ + new_expires = ktime_to_ns(timespec64_to_ktime(new->it_value)); + +- /* +- * Protect against sighand release/switch in exit/exec and p->cpu_timers +- * and p->signal->cpu_timers read/write in arm_timer() +- */ +- sighand = lock_task_sighand(p, &flags); +- /* +- * If p has just been reaped, we can no +- * longer get any information about it at all. +- */ +- if (unlikely(sighand == NULL)) { +- rcu_read_unlock(); +- return -ESRCH; +- } +- + /* + * Disarm any old timer after extracting its expiry time. + */ +@@ -659,6 +728,8 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, + old->it_value.tv_sec = 0; + } + } ++ ++ old->it_interval = ns_to_timespec64(old_incr); + } + + if (unlikely(ret)) { +@@ -669,7 +740,7 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, + * it as an overrun (thanks to bump_cpu_timer above). + */ + unlock_task_sighand(p, &flags); +- goto out; ++ return ret; + } + + if (new_expires != 0 && !(timer_flags & TIMER_ABSTIME)) { +@@ -686,7 +757,6 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, + arm_timer(timer, p); + } + +- unlock_task_sighand(p, &flags); + /* + * Install the new reload setting, and + * set up the signal and overrun bookkeeping. +@@ -703,6 +773,8 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, + timer->it_overrun_last = 0; + timer->it_overrun = -1; + ++ unlock_task_sighand(p, &flags); ++ + if (new_expires != 0 && !(val < new_expires)) { + /* + * The designated time already passed, so we notify +@@ -712,13 +784,7 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, + cpu_timer_fire(timer); + } + +- ret = 0; +- out: +- rcu_read_unlock(); +- if (old) +- old->it_interval = ns_to_timespec64(old_incr); +- +- return ret; ++ return 0; + } + + static void posix_cpu_timer_get(struct k_itimer *timer, struct itimerspec64 *itp) +@@ -984,19 +1050,12 @@ static void posix_cpu_timer_rearm(struct k_itimer *timer) + { + clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock); + struct task_struct *p; +- struct sighand_struct *sighand; + unsigned long flags; + u64 now; + +- rcu_read_lock(); +- p = cpu_timer_task_rcu(timer); +- if (!p) +- goto out; +- +- /* Protect timer list r/w in arm_timer() */ +- sighand = lock_task_sighand(p, &flags); +- if (unlikely(sighand == NULL)) +- goto out; ++ p = timer_lock_sighand(timer, &flags); ++ if (unlikely(!p)) ++ return; + + /* + * Fetch the current sample and update the timer's expiry time. +@@ -1013,8 +1072,6 @@ static void posix_cpu_timer_rearm(struct k_itimer *timer) + */ + arm_timer(timer, p); + unlock_task_sighand(p, &flags); +-out: +- rcu_read_unlock(); + } + + /** +-- +2.53.0 + diff --git a/queue-5.10/series b/queue-5.10/series index 8dafb6c3db..10dd46bc18 100644 --- a/queue-5.10/series +++ b/queue-5.10/series @@ -55,3 +55,5 @@ usb-gadget-uvc-clamp-send_response-length-to-the-response-buffer.patch usb-serial-ftdi_sio-add-support-for-e-h-fxa291.patch usb-serial-io_edgeport-cap-received-transmit-credits.patch usb-serial-option-add-tdtech-mt5710-cn.patch +posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch +crypto-rsa-pkcs1pad-don-t-warn-on-an-empty-digest.patch diff --git a/queue-5.15/crypto-rsa-pkcs1pad-don-t-warn-on-an-empty-digest.patch b/queue-5.15/crypto-rsa-pkcs1pad-don-t-warn-on-an-empty-digest.patch new file mode 100644 index 0000000000..19629d4bcd --- /dev/null +++ b/queue-5.15/crypto-rsa-pkcs1pad-don-t-warn-on-an-empty-digest.patch @@ -0,0 +1,55 @@ +From 454c9876d7c1741a946cba716a1dec8633f3b319 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Mon, 20 Jul 2026 21:15:33 +0200 +Subject: crypto: rsa-pkcs1pad: Don't WARN on an empty digest + +From: Doruk Tan Ozturk + +KEYCTL_PKEY_VERIFY lets an unprivileged caller supply a zero-length +digest (in_len == 0). keyctl_pkey_params_get_2() accepts the zero +length and the request reaches pkcs1pad_verify(), where the empty +digest is rejected but only after being passed through +WARN_ON(!req->dst_len). The warning is therefore directly +user-triggerable, and on kernels built with panic_on_warn=1 an +unprivileged process can panic the machine -- a local denial of +service. Reproduced as UID 65534 in a setuid sandbox. + +Keep rejecting the invalid request with -EINVAL, but do not emit a +warning for the user-controlled length. + +This is the 5.10.y/5.15.y form of the fix, where the length is read +directly from req->dst_len rather than cached in a digest_size local. +Mainline does not contain this code path; commit 1e562deacecc +("crypto: rsassa-pkcs1 - Migrate to sig_alg backend") removed +pkcs1pad_verify() in v6.13-rc1. This is a minimal fix for the +affected stable branches. The 6.1.y/6.6.y/6.12.y form +(WARN_ON(!digest_size)) is sent as a separate patch. + +Found by 0sec automated security-research tooling (https://0sec.ai). + +Fixes: c7381b012872 ("crypto: akcipher - new verify API for public key algorithms") +Cc: stable@vger.kernel.org +Assisted-by: 0sec:multi-model +Signed-off-by: Doruk Tan Ozturk +Reviewed-by: Lukas Wunner +Signed-off-by: Sasha Levin +--- + crypto/rsa-pkcs1pad.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/crypto/rsa-pkcs1pad.c b/crypto/rsa-pkcs1pad.c +index e2f4ccbd71dd87..d10076bb6ec237 100644 +--- a/crypto/rsa-pkcs1pad.c ++++ b/crypto/rsa-pkcs1pad.c +@@ -535,7 +535,7 @@ static int pkcs1pad_verify(struct akcipher_request *req) + int err; + + if (WARN_ON(req->dst) || +- WARN_ON(!req->dst_len) || ++ !req->dst_len || + !ctx->key_size || req->src_len != ctx->key_size) + return -EINVAL; + +-- +2.53.0 + diff --git a/queue-5.15/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch b/queue-5.15/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch new file mode 100644 index 0000000000..feb8a25c84 --- /dev/null +++ b/queue-5.15/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch @@ -0,0 +1,486 @@ +From 09486fef145315e0221c496ec7af5fc66c785357 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Mon, 27 Jul 2026 11:10:14 +0200 +Subject: posix-cpu-timers: Prevent UAF caused by non-leader exec() race + +From: Thomas Gleixner + +commit 920f893f735e92ba3a1cd9256899a186b161928d upstream. + +Wongi and Jungwoo decoded and reported a non-leader exec() related race +which can result in an UAF: + + sys_timer_delete() exec() + posix_cpu_timer_del() + // Observes old leader + p = pid_task(pid, pid_type); de_thread() + switch_leader(); + release_task(old_leader) + __exit_signal(old_leader) + sighand = lock(old_leader, sighand); + posix_cpu_timers*_exit(); + sighand = lock_task_sighand(p) unhash_task(old_leader); + sh = lock(p, sighand) old_leader->sighand = NULL; + unlock(sighand); + (p->sighand == NULL) + unlock(sh) + return NULL; + + // Returns without action + if(!sighand) + return 0; + free_posix_timer(); + +This is "harmless" unless the deleted timer was armed and enqueued in +p->signal because on exec() a TGID targeted timer is inherited. + +As sys_timer_delete() freed the underlying posix timer object +run_posix_cpu_timers() or any timerqueue related add/delete operations on +other timers will access the freed object's timerqueue node, which results +in an UAF. + +There is a similar problem vs. posix_cpu_timer_set(). For regular posix +timers it just transiently returns -ESRCH to user space, but for the use +case in do_cpu_nanosleep() it's the same UAF just that the k_itimer is +allocated on the stack. + +Also posix_cpu_timer_rearm() fails to rearm the timer, which means it stops +to expire. + +While debating solutions Frederic pointed out another problem: + + posix_cpu_timer_del(tmr) + __exit_signal(p) + posix_cpu_timers*_exit(p); + unhash_task(p); + p->sighand = NULL; + sh = lock_task_sighand(p) + sighand = p->sighand; + if (!sighand) + return NULL; + lock(sighand); + + if (!sh) + WARN_ON_ONCE(timer_queued(tmr)); + +On weakly ordered architectures it is not guaranteed that +posix_cpu_timer_del() will observe the stores in posix_cpu_timers*_exit() +when p->sighand is observed as NULL, which means the WARN() can be a false +positive. + +Solve these issues by: + + 1) Changing the store in __exit_signal() to smp_store_release(). + + 2) Adding a smp_acquire__after_ctrl_dep() into the !sighand path + of lock_task_sighand(). + + 3) Creating a helper function for looking up the task and locking sighand + which does not return when sighand == NULL. Instead it retries the + task lookup and only if that fails it gives up. + + 4) Using that helper in the three affected functions. + +#1/#2 ensures that the reader side which observes sighand == NULL also +observes all preceeding stores, i.e. the stores in posix_cpu_timers*_exit() +and the ones in unhash_task(). + +#3 ensures that the above described non-leader exec() situation is handled +gracefully. When the task lookup returns the old leader, but sighand == +NULL then it retries. In the non-leader exec() case the subsequent task +lookup will observe the new leader due to #1/#2. In normal exit() scenarios +the subsequent lookup fails. + +When the task lookup fails, the function also checks whether the timer is +still enqueued and issues a warning if that's the case. Unfortunately there +is nothing which can be done about it, but as the task is already not +longer visible the timer should not be accessed anymore. This check also +requires memory ordering, which is not provided when the first lookup +fails. To achieve that the check is preceeded by a smp_rmb() which pairs +with the smp_wmb() in write_seqlock() in __exit_signal(). That ensures that +the stores in posix_cpu_timers*_exit() are visible. + +The history of the non-leader exec() issue goes back to the early days of +posix CPU timers, which stored a pointer to the group leader task in the +timer. That obviously fails when a non-leader exec() switches the leader. +commit e0a70217107e ("posix-cpu-timers: workaround to suppress the problems +with mt exec") added a temporary workaround for that in 2010 which survived +about 10 years. The fix for the workaround changed the task pointer to a +pid pointer, but failed to see the subtle race described above. So the +Fixes tag picks that commit, which seems to be halfways accurate. + +Thanks to Frederic Weissbecker, Oleg Nesterov and Peter Zijlstra for +review, feedback and suggestions and to Wongi and Jungwoo for the excellent +bug report and analysis! + +Fixes: 55e8c8eb2c7b ("posix-cpu-timers: Store a reference to a pid not a task") +Reported-by: Wongi Lee +Reported-by: Jungwoo Lee +Signed-off-by: Thomas Gleixner +Reviewed-by: Oleg Nesterov +Cc: stable@vger.kernel.org +Signed-off-by: Sasha Levin +--- + kernel/exit.c | 8 +- + kernel/signal.c | 10 +- + kernel/time/posix-cpu-timers.c | 212 ++++++++++++++++++++------------- + 3 files changed, 143 insertions(+), 87 deletions(-) + +diff --git a/kernel/exit.c b/kernel/exit.c +index 3c6ddf7320fae6..776994c28887a6 100644 +--- a/kernel/exit.c ++++ b/kernel/exit.c +@@ -200,7 +200,13 @@ static void __exit_signal(struct task_struct *tsk) + * doing sigqueue_free() if we have SIGQUEUE_PREALLOC signals. + */ + flush_sigqueue(&tsk->pending); +- tsk->sighand = NULL; ++ ++ /* ++ * Ensure that all preceeding state is visible. Pairs with ++ * the smp_acquire__after_ctrl_dep() in the sighand == NULL ++ * path of lock_task_sighand(). ++ */ ++ smp_store_release(&tsk->sighand, NULL); + spin_unlock(&sighand->siglock); + + __cleanup_sighand(sighand); +diff --git a/kernel/signal.c b/kernel/signal.c +index 10a315e461d34b..662377d23c95d0 100644 +--- a/kernel/signal.c ++++ b/kernel/signal.c +@@ -1388,8 +1388,16 @@ struct sighand_struct *__lock_task_sighand(struct task_struct *tsk, + rcu_read_lock(); + for (;;) { + sighand = rcu_dereference(tsk->sighand); +- if (unlikely(sighand == NULL)) ++ if (unlikely(sighand == NULL)) { ++ /* ++ * Pairs with the smp_store_release() in ++ * __exit_signal(). It ensures that all state ++ * modifications to the task preceeding the store are ++ * visible to the callers of lock_task_sighand(). ++ */ ++ smp_acquire__after_ctrl_dep(); + break; ++ } + + /* + * This sighand can be already freed and even reused, but +diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c +index c99e11da83e85e..52deada50891ab 100644 +--- a/kernel/time/posix-cpu-timers.c ++++ b/kernel/time/posix-cpu-timers.c +@@ -455,6 +455,109 @@ static void disarm_timer(struct k_itimer *timer, struct task_struct *p) + trigger_base_recalc_expires(timer, p); + } + ++/* ++ * Lookup the task via timer->it.cpu.pid and attempt to lock the task's sighand. ++ * ++ * This can race with the reaping of the task: ++ * ++ * CPU0 CPU1 ++ * ++ * // Finds task ++ * p = pid_task(pid, pid_type); __exit_signal(p) ++ * lock(p, sighand); ++ * posix_cpu_timers*_exit(); ++ * sighand = lock_task_sighand(p); unhash_task(p); ++ * p->sighand = NULL; ++ * unlock(sighand); ++ * ++ * In this case sighand is NULL, which means the task and the associated timer ++ * queue cannot be longer accessed safely. ++ * ++ * __exit_signal() invokes posix_cpu_timers_exit() and if the thread group is ++ * dead it also invokes posix_cpu_timers_group_exit(). These functions delete ++ * all pending timers from the related timer queues. The POSIX timers (k_itimer) ++ * themself are still accessible, but not longer connected to the task. ++ * ++ * exec() works slightly differently. The task which exec()'s terminates all ++ * other threads in the thread group and runs __exit_signal() on them. As the ++ * thread group is not dead they only clean up the per task timers via ++ * posix_cpu_timers_exit(). ++ * ++ * As the TGID on exec() stays the same per process timers stay queued, if they ++ * are armed. This works without a problem when exec() is done by the thread ++ * group leader. If a non-leader thread exec()'s this can end up in the ++ * following scenario: ++ * ++ * CPU0 CPU1 ++ * // Returns old leader ++ * p = pid_task(pid, pid_type); de_thread() ++ * switch_leader() ++ * release_task(old leader) ++ * __exit_signal() ++ * old_leader->sighand = NULL; ++ * // Returns NULL ++ * sighand = lock_task_sighand(p) ++ * ++ * That's problematic for several functions: ++ * ++ * - posix_cpu_timer_del(): If the timer is still enqueued on the task the ++ * underlying k_itimer will be freed which results in a UAF in ++ * run_posix_cpu_timers() or on timerqueue related add/delete operations. ++ * If the timer is not enqueued, the failure is harmless ++ * ++ * - posix_cpu_timer_set(): Independent of the enqueued state that results in a ++ * transient failure which is user space visible (-ESRCH) for regular posix ++ * timers. But for the use case in do_cpu_nanosleep() it's the same UAF ++ * problem just that the timer is allocated on the stack. ++ * ++ * - posix_cpu_timer_rearm(): Timer is not enqueued at that point, but this ++ * silently ignores the rearm request, which is a functional problem as the ++ * timer wont expire anymore. ++ */ ++static struct task_struct *timer_lock_sighand(struct k_itimer *timer, unsigned long *flags) ++{ ++ enum pid_type type = clock_pid_type(timer->it_clock); ++ struct cpu_timer *ctmr = &timer->it.cpu; ++ ++ guard(rcu)(); ++ ++ for (;;) { ++ struct task_struct *t = pid_task(timer->it.cpu.pid, type); ++ ++ /* Fail if the task cannot be found. */ ++ if (!t) ++ break; ++ ++ /* Try to lock the task's sighand */ ++ if (lock_task_sighand(t, flags)) ++ return t; ++ ++ /* ++ * The next PID lookup might either fail or return the new ++ * leader. This is correct for both exit() and exec(). ++ */ ++ } ++ ++ /* ++ * If the timer is still enqueued, warn. There is nothing safe to do ++ * here as there might be two timers in there which are removed in ++ * parallel and that will cause more damage than good. This should never ++ * happen! ++ * ++ * Ensure that the stores to the timer and timerqueue are visible: ++ * ++ * __exit_signal() ++ * posix_cpu_timers*_exit() ++ * write_seqlock(seqlock) ++ * smp_wmb(); <------- ++ * __unhash_process() | !pid_task() ++ * ----> smp_rmb(); ++ * WARN_ON_ONCE(...) ++ */ ++ smp_rmb(); ++ WARN_ON_ONCE(ctmr->head || timerqueue_node_queued(&ctmr->node)); ++ return NULL; ++} + + /* + * Clean up a CPU-clock timer that is about to be destroyed. +@@ -464,29 +567,13 @@ static void disarm_timer(struct k_itimer *timer, struct task_struct *p) + */ + static int posix_cpu_timer_del(struct k_itimer *timer) + { +- struct cpu_timer *ctmr = &timer->it.cpu; +- struct sighand_struct *sighand; + struct task_struct *p; + unsigned long flags; + int ret = 0; + +- rcu_read_lock(); +- p = cpu_timer_task_rcu(timer); +- if (!p) +- goto out; ++ p = timer_lock_sighand(timer, &flags); + +- /* +- * Protect against sighand release/switch in exit/exec and process/ +- * thread timer list entry concurrent read/writes. +- */ +- sighand = lock_task_sighand(p, &flags); +- if (unlikely(sighand == NULL)) { +- /* +- * This raced with the reaping of the task. The exit cleanup +- * should have removed this timer from the timer queue. +- */ +- WARN_ON_ONCE(ctmr->head || timerqueue_node_queued(&ctmr->node)); +- } else { ++ if (likely(p)) { + if (timer->it.cpu.firing) + ret = TIMER_RETRY; + else +@@ -495,10 +582,8 @@ static int posix_cpu_timer_del(struct k_itimer *timer) + unlock_task_sighand(p, &flags); + } + +-out: +- rcu_read_unlock(); + if (!ret) +- put_pid(ctmr->pid); ++ put_pid(timer->it.cpu.pid); + + return ret; + } +@@ -620,21 +705,17 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, + clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock); + u64 old_expires, new_expires, old_incr, val; + struct cpu_timer *ctmr = &timer->it.cpu; +- struct sighand_struct *sighand; + struct task_struct *p; + unsigned long flags; + int ret = 0; + +- rcu_read_lock(); +- p = cpu_timer_task_rcu(timer); +- if (!p) { +- /* +- * If p has just been reaped, we can no +- * longer get any information about it at all. +- */ +- rcu_read_unlock(); ++ p = timer_lock_sighand(timer, &flags); ++ /* ++ * If p has just been reaped, we can no longer get any information about ++ * it at all. ++ */ ++ if (!p) + return -ESRCH; +- } + + /* + * Use the to_ktime conversion because that clamps the maximum +@@ -642,20 +723,6 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, + */ + new_expires = ktime_to_ns(timespec64_to_ktime(new->it_value)); + +- /* +- * Protect against sighand release/switch in exit/exec and p->cpu_timers +- * and p->signal->cpu_timers read/write in arm_timer() +- */ +- sighand = lock_task_sighand(p, &flags); +- /* +- * If p has just been reaped, we can no +- * longer get any information about it at all. +- */ +- if (unlikely(sighand == NULL)) { +- rcu_read_unlock(); +- return -ESRCH; +- } +- + /* + * Disarm any old timer after extracting its expiry time. + */ +@@ -704,6 +771,7 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, + old->it_value.tv_sec = 0; + } + } ++ old->it_interval = ns_to_timespec64(old_incr); + } + + if (unlikely(ret)) { +@@ -714,7 +782,7 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, + * it as an overrun (thanks to bump_cpu_timer above). + */ + unlock_task_sighand(p, &flags); +- goto out; ++ return ret; + } + + if (new_expires != 0 && !(timer_flags & TIMER_ABSTIME)) { +@@ -727,11 +795,11 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, + * arm the timer (we'll just fake it for timer_gettime). + */ + cpu_timer_setexpires(ctmr, new_expires); +- if (new_expires != 0 && val < new_expires) { ++ if (new_expires != 0 && val < new_expires) + arm_timer(timer, p); +- } ++ else ++ trigger_base_recalc_expires(timer, p); + +- unlock_task_sighand(p, &flags); + /* + * Install the new reload setting, and + * set up the signal and overrun bookkeeping. +@@ -748,35 +816,18 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, + timer->it_overrun_last = 0; + timer->it_overrun = -1; + +- if (val >= new_expires) { +- if (new_expires != 0) { +- /* +- * The designated time already passed, so we notify +- * immediately, even if the thread never runs to +- * accumulate more time on this clock. +- */ +- cpu_timer_fire(timer); +- } ++ unlock_task_sighand(p, &flags); + ++ if (new_expires && val >= new_expires) { + /* +- * Make sure we don't keep around the process wide cputime +- * counter or the tick dependency if they are not necessary. ++ * The designated time already passed, so we notify immediately, ++ * even if the thread never runs to accumulate more time on this ++ * clock. + */ +- sighand = lock_task_sighand(p, &flags); +- if (!sighand) +- goto out; +- +- if (!cpu_timer_queued(ctmr)) +- trigger_base_recalc_expires(timer, p); +- +- unlock_task_sighand(p, &flags); ++ cpu_timer_fire(timer); + } +- out: +- rcu_read_unlock(); +- if (old) +- old->it_interval = ns_to_timespec64(old_incr); + +- return ret; ++ return 0; + } + + static void posix_cpu_timer_get(struct k_itimer *timer, struct itimerspec64 *itp) +@@ -1042,19 +1093,12 @@ static void posix_cpu_timer_rearm(struct k_itimer *timer) + { + clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock); + struct task_struct *p; +- struct sighand_struct *sighand; + unsigned long flags; + u64 now; + +- rcu_read_lock(); +- p = cpu_timer_task_rcu(timer); +- if (!p) +- goto out; +- +- /* Protect timer list r/w in arm_timer() */ +- sighand = lock_task_sighand(p, &flags); +- if (unlikely(sighand == NULL)) +- goto out; ++ p = timer_lock_sighand(timer, &flags); ++ if (unlikely(!p)) ++ return; + + /* + * Fetch the current sample and update the timer's expiry time. +@@ -1071,8 +1115,6 @@ static void posix_cpu_timer_rearm(struct k_itimer *timer) + */ + arm_timer(timer, p); + unlock_task_sighand(p, &flags); +-out: +- rcu_read_unlock(); + } + + /** +-- +2.53.0 + diff --git a/queue-5.15/revert-drm-amd-display-add-missing-kdoc-for-allm-par.patch b/queue-5.15/revert-drm-amd-display-add-missing-kdoc-for-allm-par.patch new file mode 100644 index 0000000000..2d6a028074 --- /dev/null +++ b/queue-5.15/revert-drm-amd-display-add-missing-kdoc-for-allm-par.patch @@ -0,0 +1,28 @@ +From 05cc7eb7bc9503c4765275d64ca933fef4823ed0 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Mon, 27 Jul 2026 15:59:57 -0400 +Subject: Revert "drm/amd/display: Add missing kdoc for ALLM parameters" + +This reverts commit 904eed5c1044b52db948e0322a5f74ccaccd0b8e. + +Signed-off-by: Sasha Levin +--- + drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c +index c09e86523e291e..4e075b01d48bb4 100644 +--- a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c ++++ b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c +@@ -415,8 +415,6 @@ void mod_build_vsc_infopacket(const struct dc_stream_state *stream, + * + * @stream: contains data we may need to construct VSIF (i.e. timing_3d_format, etc.) + * @info_packet: output structure where to store VSIF +- * @ALLMEnabled: indicates whether ALLM HF-VSIF should be generated +- * @ALLMValue: ALLM bit value to advertise in HF-VSIF + */ + void mod_build_hf_vsif_infopacket(const struct dc_stream_state *stream, + struct dc_info_packet *info_packet) +-- +2.53.0 + diff --git a/queue-5.15/series b/queue-5.15/series index de822494fa..bbfea89b72 100644 --- a/queue-5.15/series +++ b/queue-5.15/series @@ -71,3 +71,6 @@ usb-serial-ftdi_sio-add-support-for-e-h-fxa291.patch usb-serial-io_edgeport-cap-received-transmit-credits.patch usb-serial-keyspan_pda-fix-data-loss-on-receive-throttling.patch usb-serial-option-add-tdtech-mt5710-cn.patch +posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch +crypto-rsa-pkcs1pad-don-t-warn-on-an-empty-digest.patch +revert-drm-amd-display-add-missing-kdoc-for-allm-par.patch diff --git a/queue-6.1/crypto-rsa-pkcs1pad-don-t-warn-on-an-empty-digest.patch b/queue-6.1/crypto-rsa-pkcs1pad-don-t-warn-on-an-empty-digest.patch new file mode 100644 index 0000000000..b60bf6b953 --- /dev/null +++ b/queue-6.1/crypto-rsa-pkcs1pad-don-t-warn-on-an-empty-digest.patch @@ -0,0 +1,54 @@ +From 52e95aa47635f9753c8259103a09509f94190efe Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Mon, 20 Jul 2026 21:15:25 +0200 +Subject: crypto: rsa-pkcs1pad: Don't WARN on an empty digest + +From: Doruk Tan Ozturk + +KEYCTL_PKEY_VERIFY lets an unprivileged caller supply a zero-length +digest (in_len == 0). keyctl_pkey_params_get_2() accepts the zero +length and the request reaches pkcs1pad_verify(), where the empty +digest is rejected but only after being passed through +WARN_ON(!digest_size). The warning is therefore directly +user-triggerable, and on kernels built with panic_on_warn=1 an +unprivileged process can panic the machine -- a local denial of +service. Reproduced as UID 65534 in a setuid sandbox. + +Keep rejecting the invalid request with -EINVAL, but do not emit a +warning for the user-controlled length. + +Mainline does not contain this code path; commit 1e562deacecc +("crypto: rsassa-pkcs1 - Migrate to sig_alg backend") removed +pkcs1pad_verify() in v6.13-rc1. This is a minimal fix for the +affected stable branches. It applies as-is to 6.1.y, 6.6.y and +6.12.y (identical pkcs1pad_verify); the 5.10.y/5.15.y form is sent +as a separate patch due to the older req->dst_len spelling. + +Found by 0sec automated security-research tooling (https://0sec.ai). + +Fixes: c7381b012872 ("crypto: akcipher - new verify API for public key algorithms") +Cc: stable@vger.kernel.org +Assisted-by: 0sec:multi-model +Signed-off-by: Doruk Tan Ozturk +Reviewed-by: Lukas Wunner +Signed-off-by: Sasha Levin +--- + crypto/rsa-pkcs1pad.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/crypto/rsa-pkcs1pad.c b/crypto/rsa-pkcs1pad.c +index 1cf267bc6f9ea1..5d732977bba0d4 100644 +--- a/crypto/rsa-pkcs1pad.c ++++ b/crypto/rsa-pkcs1pad.c +@@ -537,7 +537,7 @@ static int pkcs1pad_verify(struct akcipher_request *req) + const unsigned int digest_size = req->dst_len; + int err; + +- if (WARN_ON(req->dst) || WARN_ON(!digest_size) || ++ if (WARN_ON(req->dst) || !digest_size || + !ctx->key_size || sig_size != ctx->key_size) + return -EINVAL; + +-- +2.53.0 + diff --git a/queue-6.1/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch b/queue-6.1/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch new file mode 100644 index 0000000000..b7bafc9f02 --- /dev/null +++ b/queue-6.1/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch @@ -0,0 +1,486 @@ +From 30d9395052ea8c37d41b2f1329d6fadca3e801f7 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Mon, 27 Jul 2026 11:10:14 +0200 +Subject: posix-cpu-timers: Prevent UAF caused by non-leader exec() race + +From: Thomas Gleixner + +commit 920f893f735e92ba3a1cd9256899a186b161928d upstream. + +Wongi and Jungwoo decoded and reported a non-leader exec() related race +which can result in an UAF: + + sys_timer_delete() exec() + posix_cpu_timer_del() + // Observes old leader + p = pid_task(pid, pid_type); de_thread() + switch_leader(); + release_task(old_leader) + __exit_signal(old_leader) + sighand = lock(old_leader, sighand); + posix_cpu_timers*_exit(); + sighand = lock_task_sighand(p) unhash_task(old_leader); + sh = lock(p, sighand) old_leader->sighand = NULL; + unlock(sighand); + (p->sighand == NULL) + unlock(sh) + return NULL; + + // Returns without action + if(!sighand) + return 0; + free_posix_timer(); + +This is "harmless" unless the deleted timer was armed and enqueued in +p->signal because on exec() a TGID targeted timer is inherited. + +As sys_timer_delete() freed the underlying posix timer object +run_posix_cpu_timers() or any timerqueue related add/delete operations on +other timers will access the freed object's timerqueue node, which results +in an UAF. + +There is a similar problem vs. posix_cpu_timer_set(). For regular posix +timers it just transiently returns -ESRCH to user space, but for the use +case in do_cpu_nanosleep() it's the same UAF just that the k_itimer is +allocated on the stack. + +Also posix_cpu_timer_rearm() fails to rearm the timer, which means it stops +to expire. + +While debating solutions Frederic pointed out another problem: + + posix_cpu_timer_del(tmr) + __exit_signal(p) + posix_cpu_timers*_exit(p); + unhash_task(p); + p->sighand = NULL; + sh = lock_task_sighand(p) + sighand = p->sighand; + if (!sighand) + return NULL; + lock(sighand); + + if (!sh) + WARN_ON_ONCE(timer_queued(tmr)); + +On weakly ordered architectures it is not guaranteed that +posix_cpu_timer_del() will observe the stores in posix_cpu_timers*_exit() +when p->sighand is observed as NULL, which means the WARN() can be a false +positive. + +Solve these issues by: + + 1) Changing the store in __exit_signal() to smp_store_release(). + + 2) Adding a smp_acquire__after_ctrl_dep() into the !sighand path + of lock_task_sighand(). + + 3) Creating a helper function for looking up the task and locking sighand + which does not return when sighand == NULL. Instead it retries the + task lookup and only if that fails it gives up. + + 4) Using that helper in the three affected functions. + +#1/#2 ensures that the reader side which observes sighand == NULL also +observes all preceeding stores, i.e. the stores in posix_cpu_timers*_exit() +and the ones in unhash_task(). + +#3 ensures that the above described non-leader exec() situation is handled +gracefully. When the task lookup returns the old leader, but sighand == +NULL then it retries. In the non-leader exec() case the subsequent task +lookup will observe the new leader due to #1/#2. In normal exit() scenarios +the subsequent lookup fails. + +When the task lookup fails, the function also checks whether the timer is +still enqueued and issues a warning if that's the case. Unfortunately there +is nothing which can be done about it, but as the task is already not +longer visible the timer should not be accessed anymore. This check also +requires memory ordering, which is not provided when the first lookup +fails. To achieve that the check is preceeded by a smp_rmb() which pairs +with the smp_wmb() in write_seqlock() in __exit_signal(). That ensures that +the stores in posix_cpu_timers*_exit() are visible. + +The history of the non-leader exec() issue goes back to the early days of +posix CPU timers, which stored a pointer to the group leader task in the +timer. That obviously fails when a non-leader exec() switches the leader. +commit e0a70217107e ("posix-cpu-timers: workaround to suppress the problems +with mt exec") added a temporary workaround for that in 2010 which survived +about 10 years. The fix for the workaround changed the task pointer to a +pid pointer, but failed to see the subtle race described above. So the +Fixes tag picks that commit, which seems to be halfways accurate. + +Thanks to Frederic Weissbecker, Oleg Nesterov and Peter Zijlstra for +review, feedback and suggestions and to Wongi and Jungwoo for the excellent +bug report and analysis! + +Fixes: 55e8c8eb2c7b ("posix-cpu-timers: Store a reference to a pid not a task") +Reported-by: Wongi Lee +Reported-by: Jungwoo Lee +Signed-off-by: Thomas Gleixner +Reviewed-by: Oleg Nesterov +Cc: stable@vger.kernel.org +Signed-off-by: Sasha Levin +--- + kernel/exit.c | 8 +- + kernel/signal.c | 10 +- + kernel/time/posix-cpu-timers.c | 212 ++++++++++++++++++++------------- + 3 files changed, 143 insertions(+), 87 deletions(-) + +diff --git a/kernel/exit.c b/kernel/exit.c +index fef87ddfa84150..413540df3e1855 100644 +--- a/kernel/exit.c ++++ b/kernel/exit.c +@@ -203,7 +203,13 @@ static void __exit_signal(struct task_struct *tsk) + * doing sigqueue_free() if we have SIGQUEUE_PREALLOC signals. + */ + flush_sigqueue(&tsk->pending); +- tsk->sighand = NULL; ++ ++ /* ++ * Ensure that all preceeding state is visible. Pairs with ++ * the smp_acquire__after_ctrl_dep() in the sighand == NULL ++ * path of lock_task_sighand(). ++ */ ++ smp_store_release(&tsk->sighand, NULL); + spin_unlock(&sighand->siglock); + + __cleanup_sighand(sighand); +diff --git a/kernel/signal.c b/kernel/signal.c +index 98dbe713829ec3..3dd8c93c2dcc0b 100644 +--- a/kernel/signal.c ++++ b/kernel/signal.c +@@ -1395,8 +1395,16 @@ struct sighand_struct *__lock_task_sighand(struct task_struct *tsk, + rcu_read_lock(); + for (;;) { + sighand = rcu_dereference(tsk->sighand); +- if (unlikely(sighand == NULL)) ++ if (unlikely(sighand == NULL)) { ++ /* ++ * Pairs with the smp_store_release() in ++ * __exit_signal(). It ensures that all state ++ * modifications to the task preceeding the store are ++ * visible to the callers of lock_task_sighand(). ++ */ ++ smp_acquire__after_ctrl_dep(); + break; ++ } + + /* + * This sighand can be already freed and even reused, but +diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c +index c77796da8bbce5..c5f7ca683e7eba 100644 +--- a/kernel/time/posix-cpu-timers.c ++++ b/kernel/time/posix-cpu-timers.c +@@ -462,6 +462,109 @@ static void disarm_timer(struct k_itimer *timer, struct task_struct *p) + trigger_base_recalc_expires(timer, p); + } + ++/* ++ * Lookup the task via timer->it.cpu.pid and attempt to lock the task's sighand. ++ * ++ * This can race with the reaping of the task: ++ * ++ * CPU0 CPU1 ++ * ++ * // Finds task ++ * p = pid_task(pid, pid_type); __exit_signal(p) ++ * lock(p, sighand); ++ * posix_cpu_timers*_exit(); ++ * sighand = lock_task_sighand(p); unhash_task(p); ++ * p->sighand = NULL; ++ * unlock(sighand); ++ * ++ * In this case sighand is NULL, which means the task and the associated timer ++ * queue cannot be longer accessed safely. ++ * ++ * __exit_signal() invokes posix_cpu_timers_exit() and if the thread group is ++ * dead it also invokes posix_cpu_timers_group_exit(). These functions delete ++ * all pending timers from the related timer queues. The POSIX timers (k_itimer) ++ * themself are still accessible, but not longer connected to the task. ++ * ++ * exec() works slightly differently. The task which exec()'s terminates all ++ * other threads in the thread group and runs __exit_signal() on them. As the ++ * thread group is not dead they only clean up the per task timers via ++ * posix_cpu_timers_exit(). ++ * ++ * As the TGID on exec() stays the same per process timers stay queued, if they ++ * are armed. This works without a problem when exec() is done by the thread ++ * group leader. If a non-leader thread exec()'s this can end up in the ++ * following scenario: ++ * ++ * CPU0 CPU1 ++ * // Returns old leader ++ * p = pid_task(pid, pid_type); de_thread() ++ * switch_leader() ++ * release_task(old leader) ++ * __exit_signal() ++ * old_leader->sighand = NULL; ++ * // Returns NULL ++ * sighand = lock_task_sighand(p) ++ * ++ * That's problematic for several functions: ++ * ++ * - posix_cpu_timer_del(): If the timer is still enqueued on the task the ++ * underlying k_itimer will be freed which results in a UAF in ++ * run_posix_cpu_timers() or on timerqueue related add/delete operations. ++ * If the timer is not enqueued, the failure is harmless ++ * ++ * - posix_cpu_timer_set(): Independent of the enqueued state that results in a ++ * transient failure which is user space visible (-ESRCH) for regular posix ++ * timers. But for the use case in do_cpu_nanosleep() it's the same UAF ++ * problem just that the timer is allocated on the stack. ++ * ++ * - posix_cpu_timer_rearm(): Timer is not enqueued at that point, but this ++ * silently ignores the rearm request, which is a functional problem as the ++ * timer wont expire anymore. ++ */ ++static struct task_struct *timer_lock_sighand(struct k_itimer *timer, unsigned long *flags) ++{ ++ enum pid_type type = clock_pid_type(timer->it_clock); ++ struct cpu_timer *ctmr = &timer->it.cpu; ++ ++ guard(rcu)(); ++ ++ for (;;) { ++ struct task_struct *t = pid_task(timer->it.cpu.pid, type); ++ ++ /* Fail if the task cannot be found. */ ++ if (!t) ++ break; ++ ++ /* Try to lock the task's sighand */ ++ if (lock_task_sighand(t, flags)) ++ return t; ++ ++ /* ++ * The next PID lookup might either fail or return the new ++ * leader. This is correct for both exit() and exec(). ++ */ ++ } ++ ++ /* ++ * If the timer is still enqueued, warn. There is nothing safe to do ++ * here as there might be two timers in there which are removed in ++ * parallel and that will cause more damage than good. This should never ++ * happen! ++ * ++ * Ensure that the stores to the timer and timerqueue are visible: ++ * ++ * __exit_signal() ++ * posix_cpu_timers*_exit() ++ * write_seqlock(seqlock) ++ * smp_wmb(); <------- ++ * __unhash_process() | !pid_task() ++ * ----> smp_rmb(); ++ * WARN_ON_ONCE(...) ++ */ ++ smp_rmb(); ++ WARN_ON_ONCE(ctmr->head || timerqueue_node_queued(&ctmr->node)); ++ return NULL; ++} + + /* + * Clean up a CPU-clock timer that is about to be destroyed. +@@ -471,29 +574,13 @@ static void disarm_timer(struct k_itimer *timer, struct task_struct *p) + */ + static int posix_cpu_timer_del(struct k_itimer *timer) + { +- struct cpu_timer *ctmr = &timer->it.cpu; +- struct sighand_struct *sighand; + struct task_struct *p; + unsigned long flags; + int ret = 0; + +- rcu_read_lock(); +- p = cpu_timer_task_rcu(timer); +- if (!p) +- goto out; ++ p = timer_lock_sighand(timer, &flags); + +- /* +- * Protect against sighand release/switch in exit/exec and process/ +- * thread timer list entry concurrent read/writes. +- */ +- sighand = lock_task_sighand(p, &flags); +- if (unlikely(sighand == NULL)) { +- /* +- * This raced with the reaping of the task. The exit cleanup +- * should have removed this timer from the timer queue. +- */ +- WARN_ON_ONCE(ctmr->head || timerqueue_node_queued(&ctmr->node)); +- } else { ++ if (likely(p)) { + if (timer->it.cpu.firing) + ret = TIMER_RETRY; + else +@@ -502,10 +589,8 @@ static int posix_cpu_timer_del(struct k_itimer *timer) + unlock_task_sighand(p, &flags); + } + +-out: +- rcu_read_unlock(); + if (!ret) +- put_pid(ctmr->pid); ++ put_pid(timer->it.cpu.pid); + + return ret; + } +@@ -627,21 +712,17 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, + clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock); + u64 old_expires, new_expires, old_incr, val; + struct cpu_timer *ctmr = &timer->it.cpu; +- struct sighand_struct *sighand; + struct task_struct *p; + unsigned long flags; + int ret = 0; + +- rcu_read_lock(); +- p = cpu_timer_task_rcu(timer); +- if (!p) { +- /* +- * If p has just been reaped, we can no +- * longer get any information about it at all. +- */ +- rcu_read_unlock(); ++ p = timer_lock_sighand(timer, &flags); ++ /* ++ * If p has just been reaped, we can no longer get any information about ++ * it at all. ++ */ ++ if (!p) + return -ESRCH; +- } + + /* + * Use the to_ktime conversion because that clamps the maximum +@@ -649,20 +730,6 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, + */ + new_expires = ktime_to_ns(timespec64_to_ktime(new->it_value)); + +- /* +- * Protect against sighand release/switch in exit/exec and p->cpu_timers +- * and p->signal->cpu_timers read/write in arm_timer() +- */ +- sighand = lock_task_sighand(p, &flags); +- /* +- * If p has just been reaped, we can no +- * longer get any information about it at all. +- */ +- if (unlikely(sighand == NULL)) { +- rcu_read_unlock(); +- return -ESRCH; +- } +- + /* + * Disarm any old timer after extracting its expiry time. + */ +@@ -711,6 +778,7 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, + old->it_value.tv_sec = 0; + } + } ++ old->it_interval = ns_to_timespec64(old_incr); + } + + if (unlikely(ret)) { +@@ -721,7 +789,7 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, + * it as an overrun (thanks to bump_cpu_timer above). + */ + unlock_task_sighand(p, &flags); +- goto out; ++ return ret; + } + + if (new_expires != 0 && !(timer_flags & TIMER_ABSTIME)) { +@@ -734,11 +802,11 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, + * arm the timer (we'll just fake it for timer_gettime). + */ + cpu_timer_setexpires(ctmr, new_expires); +- if (new_expires != 0 && val < new_expires) { ++ if (new_expires != 0 && val < new_expires) + arm_timer(timer, p); +- } ++ else ++ trigger_base_recalc_expires(timer, p); + +- unlock_task_sighand(p, &flags); + /* + * Install the new reload setting, and + * set up the signal and overrun bookkeeping. +@@ -755,35 +823,18 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, + timer->it_overrun_last = 0; + timer->it_overrun = -1; + +- if (val >= new_expires) { +- if (new_expires != 0) { +- /* +- * The designated time already passed, so we notify +- * immediately, even if the thread never runs to +- * accumulate more time on this clock. +- */ +- cpu_timer_fire(timer); +- } ++ unlock_task_sighand(p, &flags); + ++ if (new_expires && val >= new_expires) { + /* +- * Make sure we don't keep around the process wide cputime +- * counter or the tick dependency if they are not necessary. ++ * The designated time already passed, so we notify immediately, ++ * even if the thread never runs to accumulate more time on this ++ * clock. + */ +- sighand = lock_task_sighand(p, &flags); +- if (!sighand) +- goto out; +- +- if (!cpu_timer_queued(ctmr)) +- trigger_base_recalc_expires(timer, p); +- +- unlock_task_sighand(p, &flags); ++ cpu_timer_fire(timer); + } +- out: +- rcu_read_unlock(); +- if (old) +- old->it_interval = ns_to_timespec64(old_incr); + +- return ret; ++ return 0; + } + + static void posix_cpu_timer_get(struct k_itimer *timer, struct itimerspec64 *itp) +@@ -1049,19 +1100,12 @@ static void posix_cpu_timer_rearm(struct k_itimer *timer) + { + clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock); + struct task_struct *p; +- struct sighand_struct *sighand; + unsigned long flags; + u64 now; + +- rcu_read_lock(); +- p = cpu_timer_task_rcu(timer); +- if (!p) +- goto out; +- +- /* Protect timer list r/w in arm_timer() */ +- sighand = lock_task_sighand(p, &flags); +- if (unlikely(sighand == NULL)) +- goto out; ++ p = timer_lock_sighand(timer, &flags); ++ if (unlikely(!p)) ++ return; + + /* + * Fetch the current sample and update the timer's expiry time. +@@ -1078,8 +1122,6 @@ static void posix_cpu_timer_rearm(struct k_itimer *timer) + */ + arm_timer(timer, p); + unlock_task_sighand(p, &flags); +-out: +- rcu_read_unlock(); + } + + /** +-- +2.53.0 + diff --git a/queue-6.1/revert-drm-amd-display-add-missing-kdoc-for-allm-par.patch b/queue-6.1/revert-drm-amd-display-add-missing-kdoc-for-allm-par.patch new file mode 100644 index 0000000000..5525f95e1f --- /dev/null +++ b/queue-6.1/revert-drm-amd-display-add-missing-kdoc-for-allm-par.patch @@ -0,0 +1,28 @@ +From fd28293b7ce9a579e22ab31fe6577319a6051bb5 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Mon, 27 Jul 2026 14:07:47 -0400 +Subject: Revert "drm/amd/display: Add missing kdoc for ALLM parameters" + +This reverts commit 7cb4e8ba78f96980a23be4414c8f22f417c814fa. + +Signed-off-by: Sasha Levin +--- + drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c +index 6602ac882d6bce..69691058ab8981 100644 +--- a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c ++++ b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c +@@ -445,8 +445,6 @@ void mod_build_vsc_infopacket(const struct dc_stream_state *stream, + * + * @stream: contains data we may need to construct VSIF (i.e. timing_3d_format, etc.) + * @info_packet: output structure where to store VSIF +- * @ALLMEnabled: indicates whether ALLM HF-VSIF should be generated +- * @ALLMValue: ALLM bit value to advertise in HF-VSIF + */ + void mod_build_hf_vsif_infopacket(const struct dc_stream_state *stream, + struct dc_info_packet *info_packet) +-- +2.53.0 + diff --git a/queue-6.1/series b/queue-6.1/series index ccdfdc8ccb..c486cc47b1 100644 --- a/queue-6.1/series +++ b/queue-6.1/series @@ -86,3 +86,6 @@ usb-serial-ftdi_sio-add-support-for-e-h-fxa291.patch usb-serial-io_edgeport-cap-received-transmit-credits.patch usb-serial-keyspan_pda-fix-data-loss-on-receive-throttling.patch usb-serial-option-add-tdtech-mt5710-cn.patch +posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch +crypto-rsa-pkcs1pad-don-t-warn-on-an-empty-digest.patch +revert-drm-amd-display-add-missing-kdoc-for-allm-par.patch diff --git a/queue-6.12/crypto-rsa-pkcs1pad-don-t-warn-on-an-empty-digest.patch b/queue-6.12/crypto-rsa-pkcs1pad-don-t-warn-on-an-empty-digest.patch new file mode 100644 index 0000000000..2a4cfcafd8 --- /dev/null +++ b/queue-6.12/crypto-rsa-pkcs1pad-don-t-warn-on-an-empty-digest.patch @@ -0,0 +1,54 @@ +From cff2dec617f7e17f9689a967791c295b0058e1e4 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Mon, 20 Jul 2026 21:15:25 +0200 +Subject: crypto: rsa-pkcs1pad: Don't WARN on an empty digest + +From: Doruk Tan Ozturk + +KEYCTL_PKEY_VERIFY lets an unprivileged caller supply a zero-length +digest (in_len == 0). keyctl_pkey_params_get_2() accepts the zero +length and the request reaches pkcs1pad_verify(), where the empty +digest is rejected but only after being passed through +WARN_ON(!digest_size). The warning is therefore directly +user-triggerable, and on kernels built with panic_on_warn=1 an +unprivileged process can panic the machine -- a local denial of +service. Reproduced as UID 65534 in a setuid sandbox. + +Keep rejecting the invalid request with -EINVAL, but do not emit a +warning for the user-controlled length. + +Mainline does not contain this code path; commit 1e562deacecc +("crypto: rsassa-pkcs1 - Migrate to sig_alg backend") removed +pkcs1pad_verify() in v6.13-rc1. This is a minimal fix for the +affected stable branches. It applies as-is to 6.1.y, 6.6.y and +6.12.y (identical pkcs1pad_verify); the 5.10.y/5.15.y form is sent +as a separate patch due to the older req->dst_len spelling. + +Found by 0sec automated security-research tooling (https://0sec.ai). + +Fixes: c7381b012872 ("crypto: akcipher - new verify API for public key algorithms") +Cc: stable@vger.kernel.org +Assisted-by: 0sec:multi-model +Signed-off-by: Doruk Tan Ozturk +Reviewed-by: Lukas Wunner +Signed-off-by: Sasha Levin +--- + crypto/rsa-pkcs1pad.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/crypto/rsa-pkcs1pad.c b/crypto/rsa-pkcs1pad.c +index cd501195f34a1a..225fcc3377dddc 100644 +--- a/crypto/rsa-pkcs1pad.c ++++ b/crypto/rsa-pkcs1pad.c +@@ -557,7 +557,7 @@ static int pkcs1pad_verify(struct akcipher_request *req) + const unsigned int digest_size = req->dst_len; + int err; + +- if (WARN_ON(req->dst) || WARN_ON(!digest_size) || ++ if (WARN_ON(req->dst) || !digest_size || + !ctx->key_size || sig_size != ctx->key_size) + return -EINVAL; + +-- +2.53.0 + diff --git a/queue-6.12/revert-drm-amd-display-add-missing-kdoc-for-allm-par.patch b/queue-6.12/revert-drm-amd-display-add-missing-kdoc-for-allm-par.patch new file mode 100644 index 0000000000..693735f0fc --- /dev/null +++ b/queue-6.12/revert-drm-amd-display-add-missing-kdoc-for-allm-par.patch @@ -0,0 +1,28 @@ +From 5b53041b98ddb54ee9093c538ae394e2240738da Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Mon, 27 Jul 2026 14:07:38 -0400 +Subject: Revert "drm/amd/display: Add missing kdoc for ALLM parameters" + +This reverts commit 8fa292bd7ea0c19a6f87db967db823f48542e170. + +Signed-off-by: Sasha Levin +--- + drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c +index ae858a43c35f4a..b3d55cac35694b 100644 +--- a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c ++++ b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c +@@ -447,8 +447,6 @@ void mod_build_vsc_infopacket(const struct dc_stream_state *stream, + * + * @stream: contains data we may need to construct VSIF (i.e. timing_3d_format, etc.) + * @info_packet: output structure where to store VSIF +- * @ALLMEnabled: indicates whether ALLM HF-VSIF should be generated +- * @ALLMValue: ALLM bit value to advertise in HF-VSIF + */ + void mod_build_hf_vsif_infopacket(const struct dc_stream_state *stream, + struct dc_info_packet *info_packet) +-- +2.53.0 + diff --git a/queue-6.12/series b/queue-6.12/series index 6f5e76180d..353924a5cf 100644 --- a/queue-6.12/series +++ b/queue-6.12/series @@ -129,3 +129,5 @@ usb-serial-ftdi_sio-add-support-for-e-h-fxa291.patch usb-serial-io_edgeport-cap-received-transmit-credits.patch usb-serial-keyspan_pda-fix-data-loss-on-receive-throttling.patch usb-serial-option-add-tdtech-mt5710-cn.patch +crypto-rsa-pkcs1pad-don-t-warn-on-an-empty-digest.patch +revert-drm-amd-display-add-missing-kdoc-for-allm-par.patch diff --git a/queue-6.18/revert-drm-amd-display-add-missing-kdoc-for-allm-par.patch b/queue-6.18/revert-drm-amd-display-add-missing-kdoc-for-allm-par.patch new file mode 100644 index 0000000000..50c10e1619 --- /dev/null +++ b/queue-6.18/revert-drm-amd-display-add-missing-kdoc-for-allm-par.patch @@ -0,0 +1,28 @@ +From f0815ee7d12891ba10f97521d90cdd74d962ea51 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Mon, 27 Jul 2026 14:07:33 -0400 +Subject: Revert "drm/amd/display: Add missing kdoc for ALLM parameters" + +This reverts commit 038a0f01dda595dceda8f0c9c5172b1297281929. + +Signed-off-by: Sasha Levin +--- + drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c +index ae858a43c35f4a..b3d55cac35694b 100644 +--- a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c ++++ b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c +@@ -447,8 +447,6 @@ void mod_build_vsc_infopacket(const struct dc_stream_state *stream, + * + * @stream: contains data we may need to construct VSIF (i.e. timing_3d_format, etc.) + * @info_packet: output structure where to store VSIF +- * @ALLMEnabled: indicates whether ALLM HF-VSIF should be generated +- * @ALLMValue: ALLM bit value to advertise in HF-VSIF + */ + void mod_build_hf_vsif_infopacket(const struct dc_stream_state *stream, + struct dc_info_packet *info_packet) +-- +2.53.0 + diff --git a/queue-6.18/risc-v-kvm-serialize-virtual-interrupt-pending-state.patch b/queue-6.18/risc-v-kvm-serialize-virtual-interrupt-pending-state.patch new file mode 100644 index 0000000000..525db07325 --- /dev/null +++ b/queue-6.18/risc-v-kvm-serialize-virtual-interrupt-pending-state.patch @@ -0,0 +1,396 @@ +From c041b3a03114b22703795ec90ef46da5f49b287b Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Mon, 27 Jul 2026 10:42:22 +0800 +Subject: RISC-V: KVM: Serialize virtual interrupt pending state updates + +From: Xie Bo + +commit d024a0a7879e6f37c0152aacf6d8e37b214a1738 upstream. + +KVM RISC-V tracks guest local interrupt state with two bitmaps: + + - irqs_pending: interrupts that should be visible to the guest + - irqs_pending_mask: interrupts whose pending state changed + +The current code updates those bitmaps with independent atomic bitops +and assumes a multiple-producer, single-consumer protocol. That model +does not actually hold. + +kvm_riscv_vcpu_sync_interrupts() is not a pure consumer. When the guest +changes guest-visible HVIP state, sync_interrupts() writes both +irqs_pending and irqs_pending_mask to reflect the new guest state back +into KVM state. As a result, irqs_pending and irqs_pending_mask form a +single logical state transition, but they are not updated atomically as +a pair. + +This allows a race where a newly injected interrupt is lost. For +example: + + CPU0 CPU1 + ---- ---- + kvm_riscv_vcpu_set_interrupt(VS_SOFT) + set_bit(VS_SOFT, irqs_pending) + kvm_riscv_vcpu_sync_interrupts() + sees guest-cleared HVIP.VSSIP + sets irqs_pending_mask + clear_bit(IRQ_VS_SOFT, irqs_pending) + set_bit(VS_SOFT, irqs_pending_mask) + kvm_vcpu_kick() + +After that interleaving, a later flush can update HVIP without VSSIP +even though a new virtual interrupt was injected. In practice, the +guest can remain blocked in WFI with work pending. + +The same pending/mask protocol is shared by VS soft interrupts, PMU +overflow delivery, and AIA high interrupt synchronization, so the race +is not limited to one interrupt source. + +Fix this by serializing all updates to irqs_pending and irqs_pending_mask +with a per-vCPU raw spinlock. This keeps the pending bit and the dirty +mask as one state transition across: + + - set/unset interrupt + - guest HVIP sync + - interrupt flush to guest CSR state + - vCPU reset + - AIA CSR writes that clear dirty state + +Use non-atomic bitmap operations while holding the lock. Hold the lock +across the AIA sync, flush, and pending checks as well, so both bitmap +words share the same serialization domain. + +This intentionally replaces the existing lockless protocol instead of +trying to repair it with additional barriers. The problem is not memory +ordering on a single field; it is that two separate bitmaps encode one +shared state machine while both producers and sync paths can modify +them. A per-vCPU raw spinlock keeps the fix small, local, and suitable +for backporting. + +Fixes: cce69aff689e ("RISC-V: KVM: Implement VCPU interrupts and requests handling") +Cc: stable@vger.kernel.org +Signed-off-by: Xie Bo +Reviewed-by: Anup Patel +Link: https://lore.kernel.org/r/20260715020359.1521354-2-xb@ultrarisc.com +Signed-off-by: Anup Patel + +[ bo: Adapt the AIA and general CSR helpers to their 6.18.y layout. ] + +Signed-off-by: Sasha Levin +--- + arch/riscv/include/asm/kvm_host.h | 10 ++--- + arch/riscv/kvm/aia.c | 35 ++++++++++++---- + arch/riscv/kvm/vcpu.c | 68 ++++++++++++++++++++++--------- + arch/riscv/kvm/vcpu_onereg.c | 8 +++- + 4 files changed, 87 insertions(+), 34 deletions(-) + +diff --git a/arch/riscv/include/asm/kvm_host.h b/arch/riscv/include/asm/kvm_host.h +index 4d794573e3dbe8..fd3ba183cd8241 100644 +--- a/arch/riscv/include/asm/kvm_host.h ++++ b/arch/riscv/include/asm/kvm_host.h +@@ -220,13 +220,13 @@ struct kvm_vcpu_arch { + /* + * VCPU interrupts + * +- * We have a lockless approach for tracking pending VCPU interrupts +- * implemented using atomic bitops. The irqs_pending bitmap represent +- * pending interrupts whereas irqs_pending_mask represent bits changed +- * in irqs_pending. Our approach is modeled around multiple producer +- * and single consumer problem where the consumer is the VCPU itself. ++ * The irqs_pending bitmap represents pending interrupts whereas ++ * irqs_pending_mask represents bits changed in irqs_pending. Updates ++ * to these bitmaps are serialized so vcpu interrupt sync/flush cannot ++ * drop a newly injected interrupt while syncing guest-visible HVIP. + */ + #define KVM_RISCV_VCPU_NR_IRQS 64 ++ raw_spinlock_t irqs_pending_lock; + DECLARE_BITMAP(irqs_pending, KVM_RISCV_VCPU_NR_IRQS); + DECLARE_BITMAP(irqs_pending_mask, KVM_RISCV_VCPU_NR_IRQS); + +diff --git a/arch/riscv/kvm/aia.c b/arch/riscv/kvm/aia.c +index dad3181856600f..b8b50608215564 100644 +--- a/arch/riscv/kvm/aia.c ++++ b/arch/riscv/kvm/aia.c +@@ -50,12 +50,15 @@ void kvm_riscv_vcpu_aia_flush_interrupts(struct kvm_vcpu *vcpu) + struct kvm_vcpu_aia_csr *csr = &vcpu->arch.aia_context.guest_csr; + unsigned long mask, val; + ++ lockdep_assert_held(&vcpu->arch.irqs_pending_lock); ++ + if (!kvm_riscv_aia_available()) + return; + +- if (READ_ONCE(vcpu->arch.irqs_pending_mask[1])) { +- mask = xchg_acquire(&vcpu->arch.irqs_pending_mask[1], 0); +- val = READ_ONCE(vcpu->arch.irqs_pending[1]) & mask; ++ mask = vcpu->arch.irqs_pending_mask[1]; ++ if (mask) { ++ vcpu->arch.irqs_pending_mask[1] = 0; ++ val = vcpu->arch.irqs_pending[1] & mask; + + csr->hviph &= ~mask; + csr->hviph |= val; +@@ -66,6 +69,8 @@ void kvm_riscv_vcpu_aia_sync_interrupts(struct kvm_vcpu *vcpu) + { + struct kvm_vcpu_aia_csr *csr = &vcpu->arch.aia_context.guest_csr; + ++ lockdep_assert_held(&vcpu->arch.irqs_pending_lock); ++ + if (kvm_riscv_aia_available()) + csr->vsieh = ncsr_read(CSR_VSIEH); + } +@@ -74,13 +79,22 @@ void kvm_riscv_vcpu_aia_sync_interrupts(struct kvm_vcpu *vcpu) + bool kvm_riscv_vcpu_aia_has_interrupts(struct kvm_vcpu *vcpu, u64 mask) + { + unsigned long seip; ++#ifdef CONFIG_32BIT ++ unsigned long flags; ++ bool pending; ++#endif + + if (!kvm_riscv_aia_available()) + return false; + + #ifdef CONFIG_32BIT +- if (READ_ONCE(vcpu->arch.irqs_pending[1]) & +- (vcpu->arch.aia_context.guest_csr.vsieh & upper_32_bits(mask))) ++ raw_spin_lock_irqsave(&vcpu->arch.irqs_pending_lock, flags); ++ pending = vcpu->arch.irqs_pending[1] & ++ (vcpu->arch.aia_context.guest_csr.vsieh & ++ upper_32_bits(mask)); ++ raw_spin_unlock_irqrestore(&vcpu->arch.irqs_pending_lock, flags); ++ ++ if (pending) + return true; + #endif + +@@ -198,6 +212,9 @@ int kvm_riscv_vcpu_aia_set_csr(struct kvm_vcpu *vcpu, + unsigned long val) + { + struct kvm_vcpu_aia_csr *csr = &vcpu->arch.aia_context.guest_csr; ++#ifdef CONFIG_32BIT ++ unsigned long flags; ++#endif + + if (reg_num >= sizeof(struct kvm_riscv_aia_csr) / sizeof(unsigned long)) + return -ENOENT; +@@ -206,8 +223,12 @@ int kvm_riscv_vcpu_aia_set_csr(struct kvm_vcpu *vcpu, + ((unsigned long *)csr)[reg_num] = val; + + #ifdef CONFIG_32BIT +- if (reg_num == KVM_REG_RISCV_CSR_AIA_REG(siph)) +- WRITE_ONCE(vcpu->arch.irqs_pending_mask[1], 0); ++ if (reg_num == KVM_REG_RISCV_CSR_AIA_REG(siph)) { ++ raw_spin_lock_irqsave(&vcpu->arch.irqs_pending_lock, flags); ++ vcpu->arch.irqs_pending_mask[1] = 0; ++ raw_spin_unlock_irqrestore(&vcpu->arch.irqs_pending_lock, ++ flags); ++ } + #endif + } + +diff --git a/arch/riscv/kvm/vcpu.c b/arch/riscv/kvm/vcpu.c +index d26c4967c20e24..6f8e9b105da7e4 100644 +--- a/arch/riscv/kvm/vcpu.c ++++ b/arch/riscv/kvm/vcpu.c +@@ -78,6 +78,7 @@ static void kvm_riscv_vcpu_context_reset(struct kvm_vcpu *vcpu, + + static void kvm_riscv_reset_vcpu(struct kvm_vcpu *vcpu, bool kvm_sbi_reset) + { ++ unsigned long flags; + bool loaded; + + /** +@@ -102,8 +103,10 @@ static void kvm_riscv_reset_vcpu(struct kvm_vcpu *vcpu, bool kvm_sbi_reset) + + kvm_riscv_vcpu_aia_reset(vcpu); + ++ raw_spin_lock_irqsave(&vcpu->arch.irqs_pending_lock, flags); + bitmap_zero(vcpu->arch.irqs_pending, KVM_RISCV_VCPU_NR_IRQS); + bitmap_zero(vcpu->arch.irqs_pending_mask, KVM_RISCV_VCPU_NR_IRQS); ++ raw_spin_unlock_irqrestore(&vcpu->arch.irqs_pending_lock, flags); + + kvm_riscv_vcpu_pmu_reset(vcpu); + +@@ -147,6 +150,7 @@ int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu) + + /* Setup VCPU hfence queue */ + spin_lock_init(&vcpu->arch.hfence_lock); ++ raw_spin_lock_init(&vcpu->arch.irqs_pending_lock); + + spin_lock_init(&vcpu->arch.reset_state.lock); + +@@ -348,10 +352,14 @@ void kvm_riscv_vcpu_flush_interrupts(struct kvm_vcpu *vcpu) + { + struct kvm_vcpu_csr *csr = &vcpu->arch.guest_csr; + unsigned long mask, val; ++ unsigned long flags; + +- if (READ_ONCE(vcpu->arch.irqs_pending_mask[0])) { +- mask = xchg_acquire(&vcpu->arch.irqs_pending_mask[0], 0); +- val = READ_ONCE(vcpu->arch.irqs_pending[0]) & mask; ++ raw_spin_lock_irqsave(&vcpu->arch.irqs_pending_lock, flags); ++ ++ mask = vcpu->arch.irqs_pending_mask[0]; ++ if (mask) { ++ vcpu->arch.irqs_pending_mask[0] = 0; ++ val = vcpu->arch.irqs_pending[0] & mask; + + csr->hvip &= ~mask; + csr->hvip |= val; +@@ -359,11 +367,14 @@ void kvm_riscv_vcpu_flush_interrupts(struct kvm_vcpu *vcpu) + + /* Flush AIA high interrupts */ + kvm_riscv_vcpu_aia_flush_interrupts(vcpu); ++ ++ raw_spin_unlock_irqrestore(&vcpu->arch.irqs_pending_lock, flags); + } + + void kvm_riscv_vcpu_sync_interrupts(struct kvm_vcpu *vcpu) + { + unsigned long hvip; ++ unsigned long flags; + struct kvm_vcpu_arch *v = &vcpu->arch; + struct kvm_vcpu_csr *csr = &vcpu->arch.guest_csr; + +@@ -372,34 +383,41 @@ void kvm_riscv_vcpu_sync_interrupts(struct kvm_vcpu *vcpu) + + /* Sync-up HVIP.VSSIP bit changes does by Guest */ + hvip = ncsr_read(CSR_HVIP); ++ ++ raw_spin_lock_irqsave(&v->irqs_pending_lock, flags); ++ + if ((csr->hvip ^ hvip) & (1UL << IRQ_VS_SOFT)) { + if (hvip & (1UL << IRQ_VS_SOFT)) { +- if (!test_and_set_bit(IRQ_VS_SOFT, +- v->irqs_pending_mask)) +- set_bit(IRQ_VS_SOFT, v->irqs_pending); ++ if (!__test_and_set_bit(IRQ_VS_SOFT, ++ v->irqs_pending_mask)) ++ __set_bit(IRQ_VS_SOFT, v->irqs_pending); + } else { +- if (!test_and_set_bit(IRQ_VS_SOFT, +- v->irqs_pending_mask)) +- clear_bit(IRQ_VS_SOFT, v->irqs_pending); ++ if (!__test_and_set_bit(IRQ_VS_SOFT, ++ v->irqs_pending_mask)) ++ __clear_bit(IRQ_VS_SOFT, v->irqs_pending); + } + } + + /* Sync up the HVIP.LCOFIP bit changes (only clear) by the guest */ + if ((csr->hvip ^ hvip) & (1UL << IRQ_PMU_OVF)) { + if (!(hvip & (1UL << IRQ_PMU_OVF)) && +- !test_and_set_bit(IRQ_PMU_OVF, v->irqs_pending_mask)) +- clear_bit(IRQ_PMU_OVF, v->irqs_pending); ++ !__test_and_set_bit(IRQ_PMU_OVF, v->irqs_pending_mask)) ++ __clear_bit(IRQ_PMU_OVF, v->irqs_pending); + } + + /* Sync-up AIA high interrupts */ + kvm_riscv_vcpu_aia_sync_interrupts(vcpu); + ++ raw_spin_unlock_irqrestore(&v->irqs_pending_lock, flags); ++ + /* Sync-up timer CSRs */ + kvm_riscv_vcpu_timer_sync(vcpu); + } + + int kvm_riscv_vcpu_set_interrupt(struct kvm_vcpu *vcpu, unsigned int irq) + { ++ unsigned long flags; ++ + /* + * We only allow VS-mode software, timer, and external + * interrupts when irq is one of the local interrupts +@@ -412,9 +430,10 @@ int kvm_riscv_vcpu_set_interrupt(struct kvm_vcpu *vcpu, unsigned int irq) + irq != IRQ_PMU_OVF) + return -EINVAL; + +- set_bit(irq, vcpu->arch.irqs_pending); +- smp_mb__before_atomic(); +- set_bit(irq, vcpu->arch.irqs_pending_mask); ++ raw_spin_lock_irqsave(&vcpu->arch.irqs_pending_lock, flags); ++ __set_bit(irq, vcpu->arch.irqs_pending); ++ __set_bit(irq, vcpu->arch.irqs_pending_mask); ++ raw_spin_unlock_irqrestore(&vcpu->arch.irqs_pending_lock, flags); + + kvm_vcpu_kick(vcpu); + +@@ -423,6 +442,8 @@ int kvm_riscv_vcpu_set_interrupt(struct kvm_vcpu *vcpu, unsigned int irq) + + int kvm_riscv_vcpu_unset_interrupt(struct kvm_vcpu *vcpu, unsigned int irq) + { ++ unsigned long flags; ++ + /* + * We only allow VS-mode software, timer, counter overflow and external + * interrupts when irq is one of the local interrupts +@@ -435,26 +456,33 @@ int kvm_riscv_vcpu_unset_interrupt(struct kvm_vcpu *vcpu, unsigned int irq) + irq != IRQ_PMU_OVF) + return -EINVAL; + +- clear_bit(irq, vcpu->arch.irqs_pending); +- smp_mb__before_atomic(); +- set_bit(irq, vcpu->arch.irqs_pending_mask); ++ raw_spin_lock_irqsave(&vcpu->arch.irqs_pending_lock, flags); ++ __clear_bit(irq, vcpu->arch.irqs_pending); ++ __set_bit(irq, vcpu->arch.irqs_pending_mask); ++ raw_spin_unlock_irqrestore(&vcpu->arch.irqs_pending_lock, flags); + + return 0; + } + + bool kvm_riscv_vcpu_has_interrupts(struct kvm_vcpu *vcpu, u64 mask) + { ++ unsigned long flags; + unsigned long ie; ++ bool ret; + ++ raw_spin_lock_irqsave(&vcpu->arch.irqs_pending_lock, flags); + ie = ((vcpu->arch.guest_csr.vsie & VSIP_VALID_MASK) + << VSIP_TO_HVIP_SHIFT) & (unsigned long)mask; + ie |= vcpu->arch.guest_csr.vsie & ~IRQ_LOCAL_MASK & + (unsigned long)mask; +- if (READ_ONCE(vcpu->arch.irqs_pending[0]) & ie) +- return true; ++ ret = vcpu->arch.irqs_pending[0] & ie; ++ raw_spin_unlock_irqrestore(&vcpu->arch.irqs_pending_lock, flags); + + /* Check AIA high interrupts */ +- return kvm_riscv_vcpu_aia_has_interrupts(vcpu, mask); ++ if (!ret) ++ ret = kvm_riscv_vcpu_aia_has_interrupts(vcpu, mask); ++ ++ return ret; + } + + void __kvm_riscv_vcpu_power_off(struct kvm_vcpu *vcpu) +diff --git a/arch/riscv/kvm/vcpu_onereg.c b/arch/riscv/kvm/vcpu_onereg.c +index 865dae903aa0f6..3e8f9a28341215 100644 +--- a/arch/riscv/kvm/vcpu_onereg.c ++++ b/arch/riscv/kvm/vcpu_onereg.c +@@ -522,6 +522,7 @@ static int kvm_riscv_vcpu_general_set_csr(struct kvm_vcpu *vcpu, + unsigned long reg_val) + { + struct kvm_vcpu_csr *csr = &vcpu->arch.guest_csr; ++ unsigned long flags; + + if (reg_num >= sizeof(struct kvm_riscv_csr) / sizeof(unsigned long)) + return -ENOENT; +@@ -533,8 +534,11 @@ static int kvm_riscv_vcpu_general_set_csr(struct kvm_vcpu *vcpu, + + ((unsigned long *)csr)[reg_num] = reg_val; + +- if (reg_num == KVM_REG_RISCV_CSR_REG(sip)) +- WRITE_ONCE(vcpu->arch.irqs_pending_mask[0], 0); ++ if (reg_num == KVM_REG_RISCV_CSR_REG(sip)) { ++ raw_spin_lock_irqsave(&vcpu->arch.irqs_pending_lock, flags); ++ vcpu->arch.irqs_pending_mask[0] = 0; ++ raw_spin_unlock_irqrestore(&vcpu->arch.irqs_pending_lock, flags); ++ } + + return 0; + } +-- +2.53.0 + diff --git a/queue-6.18/series b/queue-6.18/series index bcdded23bd..6577cefe1a 100644 --- a/queue-6.18/series +++ b/queue-6.18/series @@ -163,3 +163,6 @@ usb-serial-keyspan_pda-fix-data-loss-on-receive-throttling.patch usb-serial-option-add-tdtech-mt5710-cn.patch usb-typec-ucsi-detect-and-skip-duplicate-altmodes-from-buggy-firmware.patch usb-typec-ucsi-add-duplicate-detection-to-nvidia-registration-path.patch +wifi-mwifiex-fix-freeze-for-60-seconds-caused-by-req.patch +risc-v-kvm-serialize-virtual-interrupt-pending-state.patch +revert-drm-amd-display-add-missing-kdoc-for-allm-par.patch diff --git a/queue-6.18/wifi-mwifiex-fix-freeze-for-60-seconds-caused-by-req.patch b/queue-6.18/wifi-mwifiex-fix-freeze-for-60-seconds-caused-by-req.patch new file mode 100644 index 0000000000..c422976bf5 --- /dev/null +++ b/queue-6.18/wifi-mwifiex-fix-freeze-for-60-seconds-caused-by-req.patch @@ -0,0 +1,78 @@ +From 85998c10fa1ae7be3ebed1b9702a829ef4914e0a Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Mon, 13 Jul 2026 01:17:09 +0300 +Subject: wifi: mwifiex: fix freeze for 60 seconds caused by request_firmware + +From: Georgi Valkov + +[ Upstream commit 121a96c5a0db8d18e2ba2cb89660cca8a40508fe ] + +Fix regression in rgpower table loading, caused by using +request_firmware(): when the requested firmware does not exist, e.g. +nxp/rgpower_WW.bin does not exist on OpenWRT builds for WRT3200ACM, +request_firmware() falls back to firmware_fallback_sysfs(), which expects +the firmware to be provided by user space using SYSFS. No such utility is +provided in this configuration, so the entire system locks up for 60 +seconds, until the request times out. During this time, no other log +messages are observed, and the device does not respond to commands over +UART. + +The request_firmware() call is performed in the following context: +current->comm kworker/1:2 in_task 1 irqs_disabled 0 in_atomic 0 + +Fixed by using request_firmware_direct(). This prevents fallback to SYSFS, +and avoids delay. The rgpower table is optional. The driver falls back +to the device tree power table if the firmware is not present. + +The error code is printed for debugging and returned to the caller, +which only cares for success or failure, so there are no side effects. + +Fixes: 7b6f16a25806 ("wifi: mwifiex: add rgpower table loading support") +Signed-off-by: Georgi Valkov +Reviewed-by: Francesco Dolcini +Link: https://patch.msgid.link/20260712221709.7099-1-gvalkov@gmail.com +Signed-off-by: Johannes Berg +Signed-off-by: Sasha Levin +--- + drivers/net/wireless/marvell/mwifiex/sta_ioctl.c | 14 ++++++++------ + 1 file changed, 8 insertions(+), 6 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/sta_ioctl.c b/drivers/net/wireless/marvell/mwifiex/sta_ioctl.c +index ef6722ffdc74d8..358de94eeb5ed9 100644 +--- a/drivers/net/wireless/marvell/mwifiex/sta_ioctl.c ++++ b/drivers/net/wireless/marvell/mwifiex/sta_ioctl.c +@@ -196,6 +196,7 @@ static int mwifiex_request_rgpower_table(struct mwifiex_private *priv) + struct mwifiex_adapter *adapter = priv->adapter; + char rgpower_table_name[30]; + char country_code[3]; ++ int ret; + + strscpy(country_code, domain_info->country_code, sizeof(country_code)); + +@@ -214,16 +215,17 @@ static int mwifiex_request_rgpower_table(struct mwifiex_private *priv) + adapter->rgpower_data = NULL; + } + +- if ((request_firmware(&adapter->rgpower_data, rgpower_table_name, +- adapter->dev))) { ++ ret = request_firmware_direct(&adapter->rgpower_data, rgpower_table_name, ++ adapter->dev); ++ ++ if (ret) { + mwifiex_dbg( + adapter, INFO, +- "info: %s: failed to request regulatory power table\n", +- __func__); +- return -EIO; ++ "info: %s: failed to request regulatory power table: %d\n", ++ __func__, ret); + } + +- return 0; ++ return ret; + } + + static int mwifiex_dnld_rgpower_table(struct mwifiex_private *priv) +-- +2.53.0 + diff --git a/queue-6.6/crypto-rsa-pkcs1pad-don-t-warn-on-an-empty-digest.patch b/queue-6.6/crypto-rsa-pkcs1pad-don-t-warn-on-an-empty-digest.patch new file mode 100644 index 0000000000..150de7b62a --- /dev/null +++ b/queue-6.6/crypto-rsa-pkcs1pad-don-t-warn-on-an-empty-digest.patch @@ -0,0 +1,54 @@ +From 9890232304d9dde9d7d2b6ee97eddb69fbb55fc9 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Mon, 20 Jul 2026 21:15:25 +0200 +Subject: crypto: rsa-pkcs1pad: Don't WARN on an empty digest + +From: Doruk Tan Ozturk + +KEYCTL_PKEY_VERIFY lets an unprivileged caller supply a zero-length +digest (in_len == 0). keyctl_pkey_params_get_2() accepts the zero +length and the request reaches pkcs1pad_verify(), where the empty +digest is rejected but only after being passed through +WARN_ON(!digest_size). The warning is therefore directly +user-triggerable, and on kernels built with panic_on_warn=1 an +unprivileged process can panic the machine -- a local denial of +service. Reproduced as UID 65534 in a setuid sandbox. + +Keep rejecting the invalid request with -EINVAL, but do not emit a +warning for the user-controlled length. + +Mainline does not contain this code path; commit 1e562deacecc +("crypto: rsassa-pkcs1 - Migrate to sig_alg backend") removed +pkcs1pad_verify() in v6.13-rc1. This is a minimal fix for the +affected stable branches. It applies as-is to 6.1.y, 6.6.y and +6.12.y (identical pkcs1pad_verify); the 5.10.y/5.15.y form is sent +as a separate patch due to the older req->dst_len spelling. + +Found by 0sec automated security-research tooling (https://0sec.ai). + +Fixes: c7381b012872 ("crypto: akcipher - new verify API for public key algorithms") +Cc: stable@vger.kernel.org +Assisted-by: 0sec:multi-model +Signed-off-by: Doruk Tan Ozturk +Reviewed-by: Lukas Wunner +Signed-off-by: Sasha Levin +--- + crypto/rsa-pkcs1pad.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/crypto/rsa-pkcs1pad.c b/crypto/rsa-pkcs1pad.c +index d2e5e104f8cfe3..8172f0b0b481d7 100644 +--- a/crypto/rsa-pkcs1pad.c ++++ b/crypto/rsa-pkcs1pad.c +@@ -534,7 +534,7 @@ static int pkcs1pad_verify(struct akcipher_request *req) + const unsigned int digest_size = req->dst_len; + int err; + +- if (WARN_ON(req->dst) || WARN_ON(!digest_size) || ++ if (WARN_ON(req->dst) || !digest_size || + !ctx->key_size || sig_size != ctx->key_size) + return -EINVAL; + +-- +2.53.0 + diff --git a/queue-6.6/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch b/queue-6.6/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch new file mode 100644 index 0000000000..402452380d --- /dev/null +++ b/queue-6.6/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch @@ -0,0 +1,486 @@ +From df66f7b592d359455c4054a628f3ea06b1b3d806 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Mon, 27 Jul 2026 11:10:14 +0200 +Subject: posix-cpu-timers: Prevent UAF caused by non-leader exec() race + +From: Thomas Gleixner + +commit 920f893f735e92ba3a1cd9256899a186b161928d upstream. + +Wongi and Jungwoo decoded and reported a non-leader exec() related race +which can result in an UAF: + + sys_timer_delete() exec() + posix_cpu_timer_del() + // Observes old leader + p = pid_task(pid, pid_type); de_thread() + switch_leader(); + release_task(old_leader) + __exit_signal(old_leader) + sighand = lock(old_leader, sighand); + posix_cpu_timers*_exit(); + sighand = lock_task_sighand(p) unhash_task(old_leader); + sh = lock(p, sighand) old_leader->sighand = NULL; + unlock(sighand); + (p->sighand == NULL) + unlock(sh) + return NULL; + + // Returns without action + if(!sighand) + return 0; + free_posix_timer(); + +This is "harmless" unless the deleted timer was armed and enqueued in +p->signal because on exec() a TGID targeted timer is inherited. + +As sys_timer_delete() freed the underlying posix timer object +run_posix_cpu_timers() or any timerqueue related add/delete operations on +other timers will access the freed object's timerqueue node, which results +in an UAF. + +There is a similar problem vs. posix_cpu_timer_set(). For regular posix +timers it just transiently returns -ESRCH to user space, but for the use +case in do_cpu_nanosleep() it's the same UAF just that the k_itimer is +allocated on the stack. + +Also posix_cpu_timer_rearm() fails to rearm the timer, which means it stops +to expire. + +While debating solutions Frederic pointed out another problem: + + posix_cpu_timer_del(tmr) + __exit_signal(p) + posix_cpu_timers*_exit(p); + unhash_task(p); + p->sighand = NULL; + sh = lock_task_sighand(p) + sighand = p->sighand; + if (!sighand) + return NULL; + lock(sighand); + + if (!sh) + WARN_ON_ONCE(timer_queued(tmr)); + +On weakly ordered architectures it is not guaranteed that +posix_cpu_timer_del() will observe the stores in posix_cpu_timers*_exit() +when p->sighand is observed as NULL, which means the WARN() can be a false +positive. + +Solve these issues by: + + 1) Changing the store in __exit_signal() to smp_store_release(). + + 2) Adding a smp_acquire__after_ctrl_dep() into the !sighand path + of lock_task_sighand(). + + 3) Creating a helper function for looking up the task and locking sighand + which does not return when sighand == NULL. Instead it retries the + task lookup and only if that fails it gives up. + + 4) Using that helper in the three affected functions. + +#1/#2 ensures that the reader side which observes sighand == NULL also +observes all preceeding stores, i.e. the stores in posix_cpu_timers*_exit() +and the ones in unhash_task(). + +#3 ensures that the above described non-leader exec() situation is handled +gracefully. When the task lookup returns the old leader, but sighand == +NULL then it retries. In the non-leader exec() case the subsequent task +lookup will observe the new leader due to #1/#2. In normal exit() scenarios +the subsequent lookup fails. + +When the task lookup fails, the function also checks whether the timer is +still enqueued and issues a warning if that's the case. Unfortunately there +is nothing which can be done about it, but as the task is already not +longer visible the timer should not be accessed anymore. This check also +requires memory ordering, which is not provided when the first lookup +fails. To achieve that the check is preceeded by a smp_rmb() which pairs +with the smp_wmb() in write_seqlock() in __exit_signal(). That ensures that +the stores in posix_cpu_timers*_exit() are visible. + +The history of the non-leader exec() issue goes back to the early days of +posix CPU timers, which stored a pointer to the group leader task in the +timer. That obviously fails when a non-leader exec() switches the leader. +commit e0a70217107e ("posix-cpu-timers: workaround to suppress the problems +with mt exec") added a temporary workaround for that in 2010 which survived +about 10 years. The fix for the workaround changed the task pointer to a +pid pointer, but failed to see the subtle race described above. So the +Fixes tag picks that commit, which seems to be halfways accurate. + +Thanks to Frederic Weissbecker, Oleg Nesterov and Peter Zijlstra for +review, feedback and suggestions and to Wongi and Jungwoo for the excellent +bug report and analysis! + +Fixes: 55e8c8eb2c7b ("posix-cpu-timers: Store a reference to a pid not a task") +Reported-by: Wongi Lee +Reported-by: Jungwoo Lee +Signed-off-by: Thomas Gleixner +Reviewed-by: Oleg Nesterov +Cc: stable@vger.kernel.org +Signed-off-by: Sasha Levin +--- + kernel/exit.c | 8 +- + kernel/signal.c | 10 +- + kernel/time/posix-cpu-timers.c | 212 ++++++++++++++++++++------------- + 3 files changed, 143 insertions(+), 87 deletions(-) + +diff --git a/kernel/exit.c b/kernel/exit.c +index 5ebe01e8f37e15..55a8aa868298f0 100644 +--- a/kernel/exit.c ++++ b/kernel/exit.c +@@ -204,7 +204,13 @@ static void __exit_signal(struct task_struct *tsk) + * doing sigqueue_free() if we have SIGQUEUE_PREALLOC signals. + */ + flush_sigqueue(&tsk->pending); +- tsk->sighand = NULL; ++ ++ /* ++ * Ensure that all preceeding state is visible. Pairs with ++ * the smp_acquire__after_ctrl_dep() in the sighand == NULL ++ * path of lock_task_sighand(). ++ */ ++ smp_store_release(&tsk->sighand, NULL); + spin_unlock(&sighand->siglock); + + __cleanup_sighand(sighand); +diff --git a/kernel/signal.c b/kernel/signal.c +index 3a484ea4bab658..9fe386e6badfb0 100644 +--- a/kernel/signal.c ++++ b/kernel/signal.c +@@ -1408,8 +1408,16 @@ struct sighand_struct *__lock_task_sighand(struct task_struct *tsk, + rcu_read_lock(); + for (;;) { + sighand = rcu_dereference(tsk->sighand); +- if (unlikely(sighand == NULL)) ++ if (unlikely(sighand == NULL)) { ++ /* ++ * Pairs with the smp_store_release() in ++ * __exit_signal(). It ensures that all state ++ * modifications to the task preceeding the store are ++ * visible to the callers of lock_task_sighand(). ++ */ ++ smp_acquire__after_ctrl_dep(); + break; ++ } + + /* + * This sighand can be already freed and even reused, but +diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c +index b99c386c50e432..fa730130fe154b 100644 +--- a/kernel/time/posix-cpu-timers.c ++++ b/kernel/time/posix-cpu-timers.c +@@ -461,6 +461,109 @@ static void disarm_timer(struct k_itimer *timer, struct task_struct *p) + trigger_base_recalc_expires(timer, p); + } + ++/* ++ * Lookup the task via timer->it.cpu.pid and attempt to lock the task's sighand. ++ * ++ * This can race with the reaping of the task: ++ * ++ * CPU0 CPU1 ++ * ++ * // Finds task ++ * p = pid_task(pid, pid_type); __exit_signal(p) ++ * lock(p, sighand); ++ * posix_cpu_timers*_exit(); ++ * sighand = lock_task_sighand(p); unhash_task(p); ++ * p->sighand = NULL; ++ * unlock(sighand); ++ * ++ * In this case sighand is NULL, which means the task and the associated timer ++ * queue cannot be longer accessed safely. ++ * ++ * __exit_signal() invokes posix_cpu_timers_exit() and if the thread group is ++ * dead it also invokes posix_cpu_timers_group_exit(). These functions delete ++ * all pending timers from the related timer queues. The POSIX timers (k_itimer) ++ * themself are still accessible, but not longer connected to the task. ++ * ++ * exec() works slightly differently. The task which exec()'s terminates all ++ * other threads in the thread group and runs __exit_signal() on them. As the ++ * thread group is not dead they only clean up the per task timers via ++ * posix_cpu_timers_exit(). ++ * ++ * As the TGID on exec() stays the same per process timers stay queued, if they ++ * are armed. This works without a problem when exec() is done by the thread ++ * group leader. If a non-leader thread exec()'s this can end up in the ++ * following scenario: ++ * ++ * CPU0 CPU1 ++ * // Returns old leader ++ * p = pid_task(pid, pid_type); de_thread() ++ * switch_leader() ++ * release_task(old leader) ++ * __exit_signal() ++ * old_leader->sighand = NULL; ++ * // Returns NULL ++ * sighand = lock_task_sighand(p) ++ * ++ * That's problematic for several functions: ++ * ++ * - posix_cpu_timer_del(): If the timer is still enqueued on the task the ++ * underlying k_itimer will be freed which results in a UAF in ++ * run_posix_cpu_timers() or on timerqueue related add/delete operations. ++ * If the timer is not enqueued, the failure is harmless ++ * ++ * - posix_cpu_timer_set(): Independent of the enqueued state that results in a ++ * transient failure which is user space visible (-ESRCH) for regular posix ++ * timers. But for the use case in do_cpu_nanosleep() it's the same UAF ++ * problem just that the timer is allocated on the stack. ++ * ++ * - posix_cpu_timer_rearm(): Timer is not enqueued at that point, but this ++ * silently ignores the rearm request, which is a functional problem as the ++ * timer wont expire anymore. ++ */ ++static struct task_struct *timer_lock_sighand(struct k_itimer *timer, unsigned long *flags) ++{ ++ enum pid_type type = clock_pid_type(timer->it_clock); ++ struct cpu_timer *ctmr = &timer->it.cpu; ++ ++ guard(rcu)(); ++ ++ for (;;) { ++ struct task_struct *t = pid_task(timer->it.cpu.pid, type); ++ ++ /* Fail if the task cannot be found. */ ++ if (!t) ++ break; ++ ++ /* Try to lock the task's sighand */ ++ if (lock_task_sighand(t, flags)) ++ return t; ++ ++ /* ++ * The next PID lookup might either fail or return the new ++ * leader. This is correct for both exit() and exec(). ++ */ ++ } ++ ++ /* ++ * If the timer is still enqueued, warn. There is nothing safe to do ++ * here as there might be two timers in there which are removed in ++ * parallel and that will cause more damage than good. This should never ++ * happen! ++ * ++ * Ensure that the stores to the timer and timerqueue are visible: ++ * ++ * __exit_signal() ++ * posix_cpu_timers*_exit() ++ * write_seqlock(seqlock) ++ * smp_wmb(); <------- ++ * __unhash_process() | !pid_task() ++ * ----> smp_rmb(); ++ * WARN_ON_ONCE(...) ++ */ ++ smp_rmb(); ++ WARN_ON_ONCE(ctmr->head || timerqueue_node_queued(&ctmr->node)); ++ return NULL; ++} + + /* + * Clean up a CPU-clock timer that is about to be destroyed. +@@ -470,29 +573,13 @@ static void disarm_timer(struct k_itimer *timer, struct task_struct *p) + */ + static int posix_cpu_timer_del(struct k_itimer *timer) + { +- struct cpu_timer *ctmr = &timer->it.cpu; +- struct sighand_struct *sighand; + struct task_struct *p; + unsigned long flags; + int ret = 0; + +- rcu_read_lock(); +- p = cpu_timer_task_rcu(timer); +- if (!p) +- goto out; ++ p = timer_lock_sighand(timer, &flags); + +- /* +- * Protect against sighand release/switch in exit/exec and process/ +- * thread timer list entry concurrent read/writes. +- */ +- sighand = lock_task_sighand(p, &flags); +- if (unlikely(sighand == NULL)) { +- /* +- * This raced with the reaping of the task. The exit cleanup +- * should have removed this timer from the timer queue. +- */ +- WARN_ON_ONCE(ctmr->head || timerqueue_node_queued(&ctmr->node)); +- } else { ++ if (likely(p)) { + if (timer->it.cpu.firing) + ret = TIMER_RETRY; + else +@@ -501,10 +588,8 @@ static int posix_cpu_timer_del(struct k_itimer *timer) + unlock_task_sighand(p, &flags); + } + +-out: +- rcu_read_unlock(); + if (!ret) +- put_pid(ctmr->pid); ++ put_pid(timer->it.cpu.pid); + + return ret; + } +@@ -626,21 +711,17 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, + clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock); + u64 old_expires, new_expires, old_incr, val; + struct cpu_timer *ctmr = &timer->it.cpu; +- struct sighand_struct *sighand; + struct task_struct *p; + unsigned long flags; + int ret = 0; + +- rcu_read_lock(); +- p = cpu_timer_task_rcu(timer); +- if (!p) { +- /* +- * If p has just been reaped, we can no +- * longer get any information about it at all. +- */ +- rcu_read_unlock(); ++ p = timer_lock_sighand(timer, &flags); ++ /* ++ * If p has just been reaped, we can no longer get any information about ++ * it at all. ++ */ ++ if (!p) + return -ESRCH; +- } + + /* + * Use the to_ktime conversion because that clamps the maximum +@@ -648,20 +729,6 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, + */ + new_expires = ktime_to_ns(timespec64_to_ktime(new->it_value)); + +- /* +- * Protect against sighand release/switch in exit/exec and p->cpu_timers +- * and p->signal->cpu_timers read/write in arm_timer() +- */ +- sighand = lock_task_sighand(p, &flags); +- /* +- * If p has just been reaped, we can no +- * longer get any information about it at all. +- */ +- if (unlikely(sighand == NULL)) { +- rcu_read_unlock(); +- return -ESRCH; +- } +- + /* + * Disarm any old timer after extracting its expiry time. + */ +@@ -710,6 +777,7 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, + old->it_value.tv_sec = 0; + } + } ++ old->it_interval = ns_to_timespec64(old_incr); + } + + if (unlikely(ret)) { +@@ -720,7 +788,7 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, + * it as an overrun (thanks to bump_cpu_timer above). + */ + unlock_task_sighand(p, &flags); +- goto out; ++ return ret; + } + + if (new_expires != 0 && !(timer_flags & TIMER_ABSTIME)) { +@@ -733,11 +801,11 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, + * arm the timer (we'll just fake it for timer_gettime). + */ + cpu_timer_setexpires(ctmr, new_expires); +- if (new_expires != 0 && val < new_expires) { ++ if (new_expires != 0 && val < new_expires) + arm_timer(timer, p); +- } ++ else ++ trigger_base_recalc_expires(timer, p); + +- unlock_task_sighand(p, &flags); + /* + * Install the new reload setting, and + * set up the signal and overrun bookkeeping. +@@ -754,35 +822,18 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, + timer->it_overrun_last = 0; + timer->it_overrun = -1; + +- if (val >= new_expires) { +- if (new_expires != 0) { +- /* +- * The designated time already passed, so we notify +- * immediately, even if the thread never runs to +- * accumulate more time on this clock. +- */ +- cpu_timer_fire(timer); +- } ++ unlock_task_sighand(p, &flags); + ++ if (new_expires && val >= new_expires) { + /* +- * Make sure we don't keep around the process wide cputime +- * counter or the tick dependency if they are not necessary. ++ * The designated time already passed, so we notify immediately, ++ * even if the thread never runs to accumulate more time on this ++ * clock. + */ +- sighand = lock_task_sighand(p, &flags); +- if (!sighand) +- goto out; +- +- if (!cpu_timer_queued(ctmr)) +- trigger_base_recalc_expires(timer, p); +- +- unlock_task_sighand(p, &flags); ++ cpu_timer_fire(timer); + } +- out: +- rcu_read_unlock(); +- if (old) +- old->it_interval = ns_to_timespec64(old_incr); + +- return ret; ++ return 0; + } + + static void posix_cpu_timer_get(struct k_itimer *timer, struct itimerspec64 *itp) +@@ -1048,19 +1099,12 @@ static void posix_cpu_timer_rearm(struct k_itimer *timer) + { + clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock); + struct task_struct *p; +- struct sighand_struct *sighand; + unsigned long flags; + u64 now; + +- rcu_read_lock(); +- p = cpu_timer_task_rcu(timer); +- if (!p) +- goto out; +- +- /* Protect timer list r/w in arm_timer() */ +- sighand = lock_task_sighand(p, &flags); +- if (unlikely(sighand == NULL)) +- goto out; ++ p = timer_lock_sighand(timer, &flags); ++ if (unlikely(!p)) ++ return; + + /* + * Fetch the current sample and update the timer's expiry time. +@@ -1077,8 +1121,6 @@ static void posix_cpu_timer_rearm(struct k_itimer *timer) + */ + arm_timer(timer, p); + unlock_task_sighand(p, &flags); +-out: +- rcu_read_unlock(); + } + + /** +-- +2.53.0 + diff --git a/queue-6.6/revert-drm-amd-display-add-missing-kdoc-for-allm-par.patch b/queue-6.6/revert-drm-amd-display-add-missing-kdoc-for-allm-par.patch new file mode 100644 index 0000000000..2bd7d9bc5d --- /dev/null +++ b/queue-6.6/revert-drm-amd-display-add-missing-kdoc-for-allm-par.patch @@ -0,0 +1,28 @@ +From 7429cdc8ea69912041d7fb5b68abdf9f8a2ac553 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Mon, 27 Jul 2026 14:07:42 -0400 +Subject: Revert "drm/amd/display: Add missing kdoc for ALLM parameters" + +This reverts commit bca364c4c75796650e53837fbec174a866141511. + +Signed-off-by: Sasha Levin +--- + drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c +index dbb62b911c75c1..84f9b412a4f117 100644 +--- a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c ++++ b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c +@@ -447,8 +447,6 @@ void mod_build_vsc_infopacket(const struct dc_stream_state *stream, + * + * @stream: contains data we may need to construct VSIF (i.e. timing_3d_format, etc.) + * @info_packet: output structure where to store VSIF +- * @ALLMEnabled: indicates whether ALLM HF-VSIF should be generated +- * @ALLMValue: ALLM bit value to advertise in HF-VSIF + */ + void mod_build_hf_vsif_infopacket(const struct dc_stream_state *stream, + struct dc_info_packet *info_packet) +-- +2.53.0 + diff --git a/queue-6.6/series b/queue-6.6/series index bcd941e8de..39f8e62246 100644 --- a/queue-6.6/series +++ b/queue-6.6/series @@ -109,3 +109,6 @@ usb-serial-ftdi_sio-add-support-for-e-h-fxa291.patch usb-serial-io_edgeport-cap-received-transmit-credits.patch usb-serial-keyspan_pda-fix-data-loss-on-receive-throttling.patch usb-serial-option-add-tdtech-mt5710-cn.patch +posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch +crypto-rsa-pkcs1pad-don-t-warn-on-an-empty-digest.patch +revert-drm-amd-display-add-missing-kdoc-for-allm-par.patch diff --git a/queue-7.1/drm-amd-display-add-dp_skip_rbr-flag-for-nutmeg.patch b/queue-7.1/drm-amd-display-add-dp_skip_rbr-flag-for-nutmeg.patch new file mode 100644 index 0000000000..b503359b3a --- /dev/null +++ b/queue-7.1/drm-amd-display-add-dp_skip_rbr-flag-for-nutmeg.patch @@ -0,0 +1,80 @@ +From c618b36cccac0ff9b25d2d43f7031121714b8137 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 29 May 2026 11:09:09 +0200 +Subject: drm/amd/display: Add dp_skip_rbr flag for NUTMEG +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +From: Timur Kristóf + +[ Upstream commit e39b7cf5c62e027af166772e46382356ecb45c36 ] + +No functional changes. Just clean up a conceptual mismatch. + +Based on feedback on the NUTMEG code in DC, the +preferred_link_setting is meant to force the DP link to a +specific setting, meaning both the link rate and lane count +should be locked to an exact value. What NUTMEG needs is +a lower bound on the link rate, which is not the same concept. + +Implement this as a HW workaround flag instead. + +Suggested-by: Wenjing Liu +Signed-off-by: Timur Kristóf +Signed-off-by: Alex Deucher +(cherry picked from commit 871ceb853841bcaa4e6cec3723b16c4887a760be) +Cc: stable@vger.kernel.org +Signed-off-by: Sasha Levin +--- + drivers/gpu/drm/amd/display/dc/dc.h | 2 ++ + drivers/gpu/drm/amd/display/dc/link/link_detection.c | 2 +- + .../drm/amd/display/dc/link/protocols/link_dp_capability.c | 6 +++--- + 3 files changed, 6 insertions(+), 4 deletions(-) + +diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h +index 55152f12af48be..61a28e287c1b98 100644 +--- a/drivers/gpu/drm/amd/display/dc/dc.h ++++ b/drivers/gpu/drm/amd/display/dc/dc.h +@@ -1727,6 +1727,8 @@ struct dc_scratch_space { + bool dp_skip_DID2; + bool dp_skip_reset_segment; + bool dp_skip_fs_144hz; ++ /* Some DP bridges don't work with RBR and must use HBR. */ ++ bool dp_skip_rbr; + bool dp_mot_reset_segment; + /* Some USB4 docks do not handle turning off MST DSC once it has been enabled. */ + bool dpia_mst_dsc_always_on; +diff --git a/drivers/gpu/drm/amd/display/dc/link/link_detection.c b/drivers/gpu/drm/amd/display/dc/link/link_detection.c +index 794dd6a9591830..faf33e9b1c6f37 100644 +--- a/drivers/gpu/drm/amd/display/dc/link/link_detection.c ++++ b/drivers/gpu/drm/amd/display/dc/link/link_detection.c +@@ -621,7 +621,7 @@ static bool detect_dp(struct dc_link *link, + link->dpcd_caps.sink_count.bits.SINK_COUNT = 1; + /* NUTMEG requires that we use HBR, doesn't work with RBR. */ + if (link->dpcd_caps.branch_dev_id == DP_BRANCH_DEVICE_ID_00001A) +- link->preferred_link_setting.link_rate = LINK_RATE_HIGH; ++ link->wa_flags.dp_skip_rbr = true; + } + + return true; +diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c +index c9c76b8c3e3ba2..fe8420b803eeda 100644 +--- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c ++++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c +@@ -750,9 +750,9 @@ static bool decide_dp_link_settings(struct dc_link *link, struct dc_link_setting + if (req_bw > dp_link_bandwidth_kbps(link, &link->verified_link_cap)) + return false; + +- if (link->preferred_link_setting.link_rate != LINK_RATE_UNKNOWN) { +- initial_link_setting.link_rate = link->preferred_link_setting.link_rate; +- current_link_setting.link_rate = link->preferred_link_setting.link_rate; ++ if (link->wa_flags.dp_skip_rbr) { ++ initial_link_setting.link_rate = LINK_RATE_HIGH; ++ current_link_setting.link_rate = LINK_RATE_HIGH; + } + + /* search for the minimum link setting that: +-- +2.53.0 + diff --git a/queue-7.1/drm-amd-display-fix-ism-dc_lock-deadlock-during-susp.patch b/queue-7.1/drm-amd-display-fix-ism-dc_lock-deadlock-during-susp.patch new file mode 100644 index 0000000000..b237c72f9c --- /dev/null +++ b/queue-7.1/drm-amd-display-fix-ism-dc_lock-deadlock-during-susp.patch @@ -0,0 +1,193 @@ +From 62fd176e318fd76ca00f1ad8649829dd272b62b1 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 30 Apr 2026 10:08:16 +0800 +Subject: drm/amd/display: Fix ISM dc_lock deadlock during suspend + +From: Ray Wu + +[ Upstream commit 3714fe242592e3699ac5e2c19d68b275a210be7d ] + +[Why] +System hang observed during suspend/resume while video is playing. +amdgpu_dm_ism_disable() is called under dc_lock and waits for ISM +delayed work via disable_delayed_work_sync(). The work handlers +themselves take dc_lock, producing an ABBA deadlock when a worker is +in flight at suspend time. + +[How] +Split the disable path into two phases with opposite locking +contracts: + 1. amdgpu_dm_ism_disable() -- quiesces workers, must NOT hold + dc_lock. + 2. amdgpu_dm_ism_force_full_power() (new) -- drives the ISM FSM + back to FULL_POWER_RUNNING, must hold dc_lock. + +Reviewed-by: Sun peng (Leo) Li +Signed-off-by: Ray Wu +Signed-off-by: Ivan Lipski +Tested-by: Dan Wheeler +Signed-off-by: Alex Deucher +Signed-off-by: Sasha Levin +--- + .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 25 +++++++-- + .../drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c | 56 ++++++++++++++++--- + .../drm/amd/display/amdgpu_dm/amdgpu_dm_ism.h | 1 + + 3 files changed, 70 insertions(+), 12 deletions(-) + +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +index 1ecd7bef9ed850..45951d47ed58c8 100644 +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -2260,9 +2260,16 @@ static void amdgpu_dm_fini(struct amdgpu_device *adev) + adev->dm.idle_workqueue = NULL; + } + +- /* Disable ISM before dc_destroy() invalidates dm->dc */ ++ /* ++ * Disable ISM before dc_destroy() invalidates dm->dc. ++ * ++ * Quiesce workers first without dc_lock (they take dc_lock ++ * themselves, so syncing under it would deadlock), then drive the ++ * FSM back to FULL_POWER_RUNNING under dc_lock. ++ */ ++ amdgpu_dm_ism_disable(&adev->dm); + scoped_guard(mutex, &adev->dm.dc_lock) +- amdgpu_dm_ism_disable(&adev->dm); ++ amdgpu_dm_ism_force_full_power(&adev->dm); + + amdgpu_dm_destroy_drm_device(&adev->dm); + +@@ -3290,9 +3297,14 @@ static int dm_suspend(struct amdgpu_ip_block *ip_block) + if (amdgpu_in_reset(adev)) { + enum dc_status res; + ++ /* Quiesce ISM workers before taking dc_lock (workers take ++ * dc_lock themselves; syncing under it would deadlock). ++ */ ++ amdgpu_dm_ism_disable(dm); ++ + mutex_lock(&dm->dc_lock); + +- amdgpu_dm_ism_disable(dm); ++ amdgpu_dm_ism_force_full_power(dm); + dc_allow_idle_optimizations(adev->dm.dc, false); + + dm->cached_dc_state = dc_state_create_copy(dm->dc->current_state); +@@ -3326,8 +3338,13 @@ static int dm_suspend(struct amdgpu_ip_block *ip_block) + + amdgpu_dm_irq_suspend(adev); + ++ /* ++ * Quiesce ISM workers before taking dc_lock (workers take dc_lock ++ * themselves; syncing under it would deadlock). ++ */ ++ amdgpu_dm_ism_disable(dm); + scoped_guard(mutex, &dm->dc_lock) +- amdgpu_dm_ism_disable(dm); ++ amdgpu_dm_ism_force_full_power(dm); + + hpd_rx_irq_work_suspend(dm); + +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c +index a64e95860e99b3..b32c8d3ac15234 100644 +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c +@@ -524,13 +524,20 @@ static void dm_ism_sso_delayed_work_func(struct work_struct *work) + } + + /** +- * amdgpu_dm_ism_disable - Disable the ISM ++ * amdgpu_dm_ism_disable - Quiesce ISM workers + * + * @dm: The amdgpu display manager + * +- * Disable the idle state manager by disabling any ISM work, canceling pending +- * work, and waiting for in-progress work to finish. After disabling, the system +- * is left in DM_ISM_STATE_FULL_POWER_RUNNING state. ++ * Cancels and disables any pending or in-flight ISM delayed work and waits ++ * for in-progress work to finish. After this returns, no ISM worker can run ++ * and subsequent mod_delayed_work() calls become no-ops via ++ * clear_pending_if_disabled(). ++ * ++ * Must NOT be called with dc_lock held: the workers themselves take dc_lock, ++ * so a synchronous wait under dc_lock would deadlock. ++ * ++ * The caller is responsible for driving the FSM back to FULL_POWER_RUNNING ++ * (under dc_lock) by calling amdgpu_dm_ism_force_full_power(). + */ + void amdgpu_dm_ism_disable(struct amdgpu_display_manager *dm) + { +@@ -538,21 +545,54 @@ void amdgpu_dm_ism_disable(struct amdgpu_display_manager *dm) + struct amdgpu_crtc *acrtc; + struct amdgpu_dm_ism *ism; + +- ASSERT(mutex_is_locked(&dm->dc_lock)); ++ /* ++ * Caller must NOT hold dc_lock: the ISM delayed work handlers ++ * acquire dc_lock themselves, so waiting for them via ++ * disable_delayed_work_sync() while holding dc_lock would ++ * self-deadlock against an in-flight worker. ++ */ ++ lockdep_assert_not_held(&dm->dc_lock); + + drm_for_each_crtc(crtc, dm->ddev) { + acrtc = to_amdgpu_crtc(crtc); + ism = &acrtc->ism; + +- /* Cancel and disable any pending work */ + disable_delayed_work_sync(&ism->delayed_work); + disable_delayed_work_sync(&ism->sso_delayed_work); ++ } ++} ++ ++/** ++ * amdgpu_dm_ism_force_full_power - Force every CRTC's ISM FSM to FULL_POWER ++ * ++ * @dm: The amdgpu display manager ++ * ++ * Sends DM_ISM_EVENT_EXIT_IDLE_REQUESTED to every CRTC's ISM, leaving each ++ * FSM in FULL_POWER_RUNNING. Intended to be paired with ++ * amdgpu_dm_ism_disable(): callers should first quiesce workers (without ++ * dc_lock), then take dc_lock and call this helper. ++ * ++ * Must be called with dc_lock held. ++ */ ++void amdgpu_dm_ism_force_full_power(struct amdgpu_display_manager *dm) ++{ ++ struct drm_crtc *crtc; ++ struct amdgpu_crtc *acrtc; ++ ++ /* ++ * Caller must hold dc_lock: commit_event() drives the FSM and ++ * may touch dc state via dc_allow_idle_optimizations() etc. ++ */ ++ lockdep_assert_held(&dm->dc_lock); ++ ++ drm_for_each_crtc(crtc, dm->ddev) { ++ acrtc = to_amdgpu_crtc(crtc); + + /* + * When disabled, leave in FULL_POWER_RUNNING state. +- * EXIT_IDLE will not queue any work ++ * EXIT_IDLE will not queue any work. + */ +- amdgpu_dm_ism_commit_event(ism, ++ amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_EXIT_IDLE_REQUESTED); + } + } +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.h +index fde0ddc8d4e4bc..964408cd9a839c 100644 +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.h ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.h +@@ -146,6 +146,7 @@ void amdgpu_dm_ism_fini(struct amdgpu_dm_ism *ism); + void amdgpu_dm_ism_commit_event(struct amdgpu_dm_ism *ism, + enum amdgpu_dm_ism_event event); + void amdgpu_dm_ism_disable(struct amdgpu_display_manager *dm); ++void amdgpu_dm_ism_force_full_power(struct amdgpu_display_manager *dm); + void amdgpu_dm_ism_enable(struct amdgpu_display_manager *dm); + + #endif +-- +2.53.0 + diff --git a/queue-7.1/drm-amd-display-fix-preferred-link-rate-for-nutmeg.patch b/queue-7.1/drm-amd-display-fix-preferred-link-rate-for-nutmeg.patch new file mode 100644 index 0000000000..c08293fd8d --- /dev/null +++ b/queue-7.1/drm-amd-display-fix-preferred-link-rate-for-nutmeg.patch @@ -0,0 +1,52 @@ +From a06719574023f83ea17471ce958fd9fd88f598d8 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 29 May 2026 11:09:08 +0200 +Subject: drm/amd/display: Fix preferred link rate for NUTMEG +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +From: Timur Kristóf + +[ Upstream commit 9fb646bc4d87f62bcbf0a7ea326430eb802c475c ] + +When there is a preferred link rate setting, it needs to be +applied to both the current and initial link rate. +This was regressed by a "coding style" fix, which caused +the current link rate to not respect the preferred value. + +This commit restores the functionality of NUTMEG, +the DP bridge encoder found on old APUs such as Kaveri. + +Fixes: a62346043a89 ("drm/amd/display: Fix coding style issue") +Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5465 +Cc: Chuanyu Tseng +Reviewed-by: Fangzhi Zuo +Signed-off-by: Timur Kristóf +Signed-off-by: Alex Deucher +(cherry picked from commit e78b0a367f8690b682029d90e75308dc84ed51de) +Cc: stable@vger.kernel.org +Signed-off-by: Sasha Levin +--- + .../drm/amd/display/dc/link/protocols/link_dp_capability.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c +index 782a45caa13d4a..c9c76b8c3e3ba2 100644 +--- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c ++++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_capability.c +@@ -750,8 +750,10 @@ static bool decide_dp_link_settings(struct dc_link *link, struct dc_link_setting + if (req_bw > dp_link_bandwidth_kbps(link, &link->verified_link_cap)) + return false; + +- if (link->preferred_link_setting.link_rate != LINK_RATE_UNKNOWN) ++ if (link->preferred_link_setting.link_rate != LINK_RATE_UNKNOWN) { + initial_link_setting.link_rate = link->preferred_link_setting.link_rate; ++ current_link_setting.link_rate = link->preferred_link_setting.link_rate; ++ } + + /* search for the minimum link setting that: + * 1. is supported according to the link training result +-- +2.53.0 + diff --git a/queue-7.1/revert-drm-amd-display-add-missing-kdoc-for-allm-par.patch b/queue-7.1/revert-drm-amd-display-add-missing-kdoc-for-allm-par.patch new file mode 100644 index 0000000000..2e0d1dc02b --- /dev/null +++ b/queue-7.1/revert-drm-amd-display-add-missing-kdoc-for-allm-par.patch @@ -0,0 +1,28 @@ +From bc474ad336d937459a848d014056c508fe1987e9 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Mon, 27 Jul 2026 14:07:29 -0400 +Subject: Revert "drm/amd/display: Add missing kdoc for ALLM parameters" + +This reverts commit 994a42b890cef46cd576a3bf700f1dbd6bb21346. + +Signed-off-by: Sasha Levin +--- + drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c +index 614db22d62f38b..00473c6284d5cc 100644 +--- a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c ++++ b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c +@@ -502,8 +502,6 @@ void mod_build_vsc_infopacket(const struct dc_stream_state *stream, + * + * @stream: contains data we may need to construct VSIF (i.e. timing_3d_format, etc.) + * @info_packet: output structure where to store VSIF +- * @ALLMEnabled: indicates whether ALLM HF-VSIF should be generated +- * @ALLMValue: ALLM bit value to advertise in HF-VSIF + */ + void mod_build_hf_vsif_infopacket(const struct dc_stream_state *stream, + struct dc_info_packet *info_packet) +-- +2.53.0 + diff --git a/queue-7.1/risc-v-kvm-serialize-virtual-interrupt-pending-state.patch b/queue-7.1/risc-v-kvm-serialize-virtual-interrupt-pending-state.patch new file mode 100644 index 0000000000..e10e17f5e0 --- /dev/null +++ b/queue-7.1/risc-v-kvm-serialize-virtual-interrupt-pending-state.patch @@ -0,0 +1,397 @@ +From 4d834797fcf7abeba7225c2fed88a07932b35a56 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 15 Jul 2026 10:03:59 +0800 +Subject: RISC-V: KVM: Serialize virtual interrupt pending state updates + +From: Xie Bo + +commit d024a0a7879e6f37c0152aacf6d8e37b214a1738 upstream. + +KVM RISC-V tracks guest local interrupt state with two bitmaps: + + - irqs_pending: interrupts that should be visible to the guest + - irqs_pending_mask: interrupts whose pending state changed + +The current code updates those bitmaps with independent atomic bitops +and assumes a multiple-producer, single-consumer protocol. That model +does not actually hold. + +kvm_riscv_vcpu_sync_interrupts() is not a pure consumer. When the guest +changes guest-visible HVIP state, sync_interrupts() writes both +irqs_pending and irqs_pending_mask to reflect the new guest state back +into KVM state. As a result, irqs_pending and irqs_pending_mask form a +single logical state transition, but they are not updated atomically as +a pair. + +This allows a race where a newly injected interrupt is lost. For +example: + + CPU0 CPU1 + ---- ---- + kvm_riscv_vcpu_set_interrupt(VS_SOFT) + set_bit(VS_SOFT, irqs_pending) + kvm_riscv_vcpu_sync_interrupts() + sees guest-cleared HVIP.VSSIP + sets irqs_pending_mask + clear_bit(IRQ_VS_SOFT, irqs_pending) + set_bit(VS_SOFT, irqs_pending_mask) + kvm_vcpu_kick() + +After that interleaving, a later flush can update HVIP without VSSIP +even though a new virtual interrupt was injected. In practice, the +guest can remain blocked in WFI with work pending. + +The same pending/mask protocol is shared by VS soft interrupts, PMU +overflow delivery, and AIA high interrupt synchronization, so the race +is not limited to one interrupt source. + +Fix this by serializing all updates to irqs_pending and irqs_pending_mask +with a per-vCPU raw spinlock. This keeps the pending bit and the dirty +mask as one state transition across: + + - set/unset interrupt + - guest HVIP sync + - interrupt flush to guest CSR state + - vCPU reset + - AIA CSR writes that clear dirty state + +Use non-atomic bitmap operations while holding the lock. Hold the lock +across the AIA sync, flush, and pending checks as well, so both bitmap +words share the same serialization domain. + +This intentionally replaces the existing lockless protocol instead of +trying to repair it with additional barriers. The problem is not memory +ordering on a single field; it is that two separate bitmaps encode one +shared state machine while both producers and sync paths can modify +them. A per-vCPU raw spinlock keeps the fix small, local, and suitable +for backporting. + +Fixes: cce69aff689e ("RISC-V: KVM: Implement VCPU interrupts and requests handling") +Cc: stable@vger.kernel.org +[ Adapted for 7.1.y: place the new 'unsigned long flags;' declaration in + kvm_riscv_vcpu_general_set_csr() explicitly. A direct apply anchors it in + kvm_riscv_vcpu_general_get_csr(), whose prologue is byte-identical, leaving + 'flags' undeclared at its use site. ] + +Signed-off-by: Xie Bo +Reviewed-by: Anup Patel +Signed-off-by: Anup Patel +Signed-off-by: Sasha Levin +--- + arch/riscv/include/asm/kvm_host.h | 10 ++--- + arch/riscv/kvm/aia.c | 35 ++++++++++++---- + arch/riscv/kvm/vcpu.c | 68 ++++++++++++++++++++++--------- + arch/riscv/kvm/vcpu_onereg.c | 8 +++- + 4 files changed, 87 insertions(+), 34 deletions(-) + +diff --git a/arch/riscv/include/asm/kvm_host.h b/arch/riscv/include/asm/kvm_host.h +index 75b0a951c1bc6f..97e42645cbabee 100644 +--- a/arch/riscv/include/asm/kvm_host.h ++++ b/arch/riscv/include/asm/kvm_host.h +@@ -207,13 +207,13 @@ struct kvm_vcpu_arch { + /* + * VCPU interrupts + * +- * We have a lockless approach for tracking pending VCPU interrupts +- * implemented using atomic bitops. The irqs_pending bitmap represent +- * pending interrupts whereas irqs_pending_mask represent bits changed +- * in irqs_pending. Our approach is modeled around multiple producer +- * and single consumer problem where the consumer is the VCPU itself. ++ * The irqs_pending bitmap represents pending interrupts whereas ++ * irqs_pending_mask represents bits changed in irqs_pending. Updates ++ * to these bitmaps are serialized so vcpu interrupt sync/flush cannot ++ * drop a newly injected interrupt while syncing guest-visible HVIP. + */ + #define KVM_RISCV_VCPU_NR_IRQS 64 ++ raw_spinlock_t irqs_pending_lock; + DECLARE_BITMAP(irqs_pending, KVM_RISCV_VCPU_NR_IRQS); + DECLARE_BITMAP(irqs_pending_mask, KVM_RISCV_VCPU_NR_IRQS); + +diff --git a/arch/riscv/kvm/aia.c b/arch/riscv/kvm/aia.c +index 5ec503288555d1..5e92fe14f79ae7 100644 +--- a/arch/riscv/kvm/aia.c ++++ b/arch/riscv/kvm/aia.c +@@ -51,12 +51,15 @@ void kvm_riscv_vcpu_aia_flush_interrupts(struct kvm_vcpu *vcpu) + struct kvm_vcpu_aia_csr *csr = &vcpu->arch.aia_context.guest_csr; + unsigned long mask, val; + ++ lockdep_assert_held(&vcpu->arch.irqs_pending_lock); ++ + if (!kvm_riscv_aia_available()) + return; + +- if (READ_ONCE(vcpu->arch.irqs_pending_mask[1])) { +- mask = xchg_acquire(&vcpu->arch.irqs_pending_mask[1], 0); +- val = READ_ONCE(vcpu->arch.irqs_pending[1]) & mask; ++ mask = vcpu->arch.irqs_pending_mask[1]; ++ if (mask) { ++ vcpu->arch.irqs_pending_mask[1] = 0; ++ val = vcpu->arch.irqs_pending[1] & mask; + + csr->hviph &= ~mask; + csr->hviph |= val; +@@ -67,6 +70,8 @@ void kvm_riscv_vcpu_aia_sync_interrupts(struct kvm_vcpu *vcpu) + { + struct kvm_vcpu_aia_csr *csr = &vcpu->arch.aia_context.guest_csr; + ++ lockdep_assert_held(&vcpu->arch.irqs_pending_lock); ++ + if (kvm_riscv_aia_available()) + csr->vsieh = ncsr_read(CSR_VSIEH); + } +@@ -75,13 +80,22 @@ void kvm_riscv_vcpu_aia_sync_interrupts(struct kvm_vcpu *vcpu) + bool kvm_riscv_vcpu_aia_has_interrupts(struct kvm_vcpu *vcpu, u64 mask) + { + unsigned long seip; ++#ifdef CONFIG_32BIT ++ unsigned long flags; ++ bool pending; ++#endif + + if (!kvm_riscv_aia_available()) + return false; + + #ifdef CONFIG_32BIT +- if (READ_ONCE(vcpu->arch.irqs_pending[1]) & +- (vcpu->arch.aia_context.guest_csr.vsieh & upper_32_bits(mask))) ++ raw_spin_lock_irqsave(&vcpu->arch.irqs_pending_lock, flags); ++ pending = vcpu->arch.irqs_pending[1] & ++ (vcpu->arch.aia_context.guest_csr.vsieh & ++ upper_32_bits(mask)); ++ raw_spin_unlock_irqrestore(&vcpu->arch.irqs_pending_lock, flags); ++ ++ if (pending) + return true; + #endif + +@@ -205,6 +219,9 @@ int kvm_riscv_vcpu_aia_set_csr(struct kvm_vcpu *vcpu, + { + struct kvm_vcpu_aia_csr *csr = &vcpu->arch.aia_context.guest_csr; + unsigned long regs_max = sizeof(struct kvm_riscv_aia_csr) / sizeof(unsigned long); ++#ifdef CONFIG_32BIT ++ unsigned long flags; ++#endif + + if (!riscv_isa_extension_available(vcpu->arch.isa, SSAIA)) + return -ENOENT; +@@ -217,8 +234,12 @@ int kvm_riscv_vcpu_aia_set_csr(struct kvm_vcpu *vcpu, + ((unsigned long *)csr)[reg_num] = val; + + #ifdef CONFIG_32BIT +- if (reg_num == KVM_REG_RISCV_CSR_AIA_REG(siph)) +- WRITE_ONCE(vcpu->arch.irqs_pending_mask[1], 0); ++ if (reg_num == KVM_REG_RISCV_CSR_AIA_REG(siph)) { ++ raw_spin_lock_irqsave(&vcpu->arch.irqs_pending_lock, flags); ++ vcpu->arch.irqs_pending_mask[1] = 0; ++ raw_spin_unlock_irqrestore(&vcpu->arch.irqs_pending_lock, ++ flags); ++ } + #endif + } + +diff --git a/arch/riscv/kvm/vcpu.c b/arch/riscv/kvm/vcpu.c +index a73690eda84b5a..61b3029080b1f8 100644 +--- a/arch/riscv/kvm/vcpu.c ++++ b/arch/riscv/kvm/vcpu.c +@@ -80,6 +80,7 @@ static void kvm_riscv_vcpu_context_reset(struct kvm_vcpu *vcpu, + + static void kvm_riscv_reset_vcpu(struct kvm_vcpu *vcpu, bool kvm_sbi_reset) + { ++ unsigned long flags; + bool loaded; + + /** +@@ -104,8 +105,10 @@ static void kvm_riscv_reset_vcpu(struct kvm_vcpu *vcpu, bool kvm_sbi_reset) + + kvm_riscv_vcpu_aia_reset(vcpu); + ++ raw_spin_lock_irqsave(&vcpu->arch.irqs_pending_lock, flags); + bitmap_zero(vcpu->arch.irqs_pending, KVM_RISCV_VCPU_NR_IRQS); + bitmap_zero(vcpu->arch.irqs_pending_mask, KVM_RISCV_VCPU_NR_IRQS); ++ raw_spin_unlock_irqrestore(&vcpu->arch.irqs_pending_lock, flags); + + kvm_riscv_vcpu_pmu_reset(vcpu); + +@@ -151,6 +154,7 @@ int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu) + + /* Setup VCPU hfence queue */ + spin_lock_init(&vcpu->arch.hfence_lock); ++ raw_spin_lock_init(&vcpu->arch.irqs_pending_lock); + + spin_lock_init(&vcpu->arch.reset_state.lock); + +@@ -352,10 +356,14 @@ void kvm_riscv_vcpu_flush_interrupts(struct kvm_vcpu *vcpu) + { + struct kvm_vcpu_csr *csr = &vcpu->arch.guest_csr; + unsigned long mask, val; ++ unsigned long flags; + +- if (READ_ONCE(vcpu->arch.irqs_pending_mask[0])) { +- mask = xchg_acquire(&vcpu->arch.irqs_pending_mask[0], 0); +- val = READ_ONCE(vcpu->arch.irqs_pending[0]) & mask; ++ raw_spin_lock_irqsave(&vcpu->arch.irqs_pending_lock, flags); ++ ++ mask = vcpu->arch.irqs_pending_mask[0]; ++ if (mask) { ++ vcpu->arch.irqs_pending_mask[0] = 0; ++ val = vcpu->arch.irqs_pending[0] & mask; + + csr->hvip &= ~mask; + csr->hvip |= val; +@@ -363,11 +371,14 @@ void kvm_riscv_vcpu_flush_interrupts(struct kvm_vcpu *vcpu) + + /* Flush AIA high interrupts */ + kvm_riscv_vcpu_aia_flush_interrupts(vcpu); ++ ++ raw_spin_unlock_irqrestore(&vcpu->arch.irqs_pending_lock, flags); + } + + void kvm_riscv_vcpu_sync_interrupts(struct kvm_vcpu *vcpu) + { + unsigned long hvip; ++ unsigned long flags; + struct kvm_vcpu_arch *v = &vcpu->arch; + struct kvm_vcpu_csr *csr = &vcpu->arch.guest_csr; + +@@ -376,34 +387,41 @@ void kvm_riscv_vcpu_sync_interrupts(struct kvm_vcpu *vcpu) + + /* Sync-up HVIP.VSSIP bit changes does by Guest */ + hvip = ncsr_read(CSR_HVIP); ++ ++ raw_spin_lock_irqsave(&v->irqs_pending_lock, flags); ++ + if ((csr->hvip ^ hvip) & (1UL << IRQ_VS_SOFT)) { + if (hvip & (1UL << IRQ_VS_SOFT)) { +- if (!test_and_set_bit(IRQ_VS_SOFT, +- v->irqs_pending_mask)) +- set_bit(IRQ_VS_SOFT, v->irqs_pending); ++ if (!__test_and_set_bit(IRQ_VS_SOFT, ++ v->irqs_pending_mask)) ++ __set_bit(IRQ_VS_SOFT, v->irqs_pending); + } else { +- if (!test_and_set_bit(IRQ_VS_SOFT, +- v->irqs_pending_mask)) +- clear_bit(IRQ_VS_SOFT, v->irqs_pending); ++ if (!__test_and_set_bit(IRQ_VS_SOFT, ++ v->irqs_pending_mask)) ++ __clear_bit(IRQ_VS_SOFT, v->irqs_pending); + } + } + + /* Sync up the HVIP.LCOFIP bit changes (only clear) by the guest */ + if ((csr->hvip ^ hvip) & (1UL << IRQ_PMU_OVF)) { + if (!(hvip & (1UL << IRQ_PMU_OVF)) && +- !test_and_set_bit(IRQ_PMU_OVF, v->irqs_pending_mask)) +- clear_bit(IRQ_PMU_OVF, v->irqs_pending); ++ !__test_and_set_bit(IRQ_PMU_OVF, v->irqs_pending_mask)) ++ __clear_bit(IRQ_PMU_OVF, v->irqs_pending); + } + + /* Sync-up AIA high interrupts */ + kvm_riscv_vcpu_aia_sync_interrupts(vcpu); + ++ raw_spin_unlock_irqrestore(&v->irqs_pending_lock, flags); ++ + /* Sync-up timer CSRs */ + kvm_riscv_vcpu_timer_sync(vcpu); + } + + int kvm_riscv_vcpu_set_interrupt(struct kvm_vcpu *vcpu, unsigned int irq) + { ++ unsigned long flags; ++ + /* + * We only allow VS-mode software, timer, and external + * interrupts when irq is one of the local interrupts +@@ -416,9 +434,10 @@ int kvm_riscv_vcpu_set_interrupt(struct kvm_vcpu *vcpu, unsigned int irq) + irq != IRQ_PMU_OVF) + return -EINVAL; + +- set_bit(irq, vcpu->arch.irqs_pending); +- smp_mb__before_atomic(); +- set_bit(irq, vcpu->arch.irqs_pending_mask); ++ raw_spin_lock_irqsave(&vcpu->arch.irqs_pending_lock, flags); ++ __set_bit(irq, vcpu->arch.irqs_pending); ++ __set_bit(irq, vcpu->arch.irqs_pending_mask); ++ raw_spin_unlock_irqrestore(&vcpu->arch.irqs_pending_lock, flags); + + kvm_vcpu_kick(vcpu); + +@@ -427,6 +446,8 @@ int kvm_riscv_vcpu_set_interrupt(struct kvm_vcpu *vcpu, unsigned int irq) + + int kvm_riscv_vcpu_unset_interrupt(struct kvm_vcpu *vcpu, unsigned int irq) + { ++ unsigned long flags; ++ + /* + * We only allow VS-mode software, timer, counter overflow and external + * interrupts when irq is one of the local interrupts +@@ -439,26 +460,33 @@ int kvm_riscv_vcpu_unset_interrupt(struct kvm_vcpu *vcpu, unsigned int irq) + irq != IRQ_PMU_OVF) + return -EINVAL; + +- clear_bit(irq, vcpu->arch.irqs_pending); +- smp_mb__before_atomic(); +- set_bit(irq, vcpu->arch.irqs_pending_mask); ++ raw_spin_lock_irqsave(&vcpu->arch.irqs_pending_lock, flags); ++ __clear_bit(irq, vcpu->arch.irqs_pending); ++ __set_bit(irq, vcpu->arch.irqs_pending_mask); ++ raw_spin_unlock_irqrestore(&vcpu->arch.irqs_pending_lock, flags); + + return 0; + } + + bool kvm_riscv_vcpu_has_interrupts(struct kvm_vcpu *vcpu, u64 mask) + { ++ unsigned long flags; + unsigned long ie; ++ bool ret; + ++ raw_spin_lock_irqsave(&vcpu->arch.irqs_pending_lock, flags); + ie = ((vcpu->arch.guest_csr.vsie & VSIP_VALID_MASK) + << VSIP_TO_HVIP_SHIFT) & (unsigned long)mask; + ie |= vcpu->arch.guest_csr.vsie & ~IRQ_LOCAL_MASK & + (unsigned long)mask; +- if (READ_ONCE(vcpu->arch.irqs_pending[0]) & ie) +- return true; ++ ret = vcpu->arch.irqs_pending[0] & ie; ++ raw_spin_unlock_irqrestore(&vcpu->arch.irqs_pending_lock, flags); + + /* Check AIA high interrupts */ +- return kvm_riscv_vcpu_aia_has_interrupts(vcpu, mask); ++ if (!ret) ++ ret = kvm_riscv_vcpu_aia_has_interrupts(vcpu, mask); ++ ++ return ret; + } + + void __kvm_riscv_vcpu_power_off(struct kvm_vcpu *vcpu) +diff --git a/arch/riscv/kvm/vcpu_onereg.c b/arch/riscv/kvm/vcpu_onereg.c +index bb920e8923c930..2031beb9bba6e7 100644 +--- a/arch/riscv/kvm/vcpu_onereg.c ++++ b/arch/riscv/kvm/vcpu_onereg.c +@@ -298,6 +298,7 @@ static int kvm_riscv_vcpu_general_set_csr(struct kvm_vcpu *vcpu, + { + struct kvm_vcpu_csr *csr = &vcpu->arch.guest_csr; + unsigned long regs_max = sizeof(struct kvm_riscv_csr) / sizeof(unsigned long); ++ unsigned long flags; + + if (reg_num >= regs_max) + return -ENOENT; +@@ -311,8 +312,11 @@ static int kvm_riscv_vcpu_general_set_csr(struct kvm_vcpu *vcpu, + + ((unsigned long *)csr)[reg_num] = reg_val; + +- if (reg_num == KVM_REG_RISCV_CSR_REG(sip)) +- WRITE_ONCE(vcpu->arch.irqs_pending_mask[0], 0); ++ if (reg_num == KVM_REG_RISCV_CSR_REG(sip)) { ++ raw_spin_lock_irqsave(&vcpu->arch.irqs_pending_lock, flags); ++ vcpu->arch.irqs_pending_mask[0] = 0; ++ raw_spin_unlock_irqrestore(&vcpu->arch.irqs_pending_lock, flags); ++ } + + return 0; + } +-- +2.53.0 + diff --git a/queue-7.1/series b/queue-7.1/series index a1786d4dc9..ab5db8cdbe 100644 --- a/queue-7.1/series +++ b/queue-7.1/series @@ -156,3 +156,9 @@ usb-serial-keyspan_pda-fix-data-loss-on-receive-throttling.patch usb-serial-option-add-tdtech-mt5710-cn.patch usb-typec-ucsi-detect-and-skip-duplicate-altmodes-from-buggy-firmware.patch usb-typec-ucsi-add-duplicate-detection-to-nvidia-registration-path.patch +drm-amd-display-fix-ism-dc_lock-deadlock-during-susp.patch +drm-amd-display-fix-preferred-link-rate-for-nutmeg.patch +drm-amd-display-add-dp_skip_rbr-flag-for-nutmeg.patch +wifi-mwifiex-fix-freeze-for-60-seconds-caused-by-req.patch +risc-v-kvm-serialize-virtual-interrupt-pending-state.patch +revert-drm-amd-display-add-missing-kdoc-for-allm-par.patch diff --git a/queue-7.1/wifi-mwifiex-fix-freeze-for-60-seconds-caused-by-req.patch b/queue-7.1/wifi-mwifiex-fix-freeze-for-60-seconds-caused-by-req.patch new file mode 100644 index 0000000000..be2403c11f --- /dev/null +++ b/queue-7.1/wifi-mwifiex-fix-freeze-for-60-seconds-caused-by-req.patch @@ -0,0 +1,78 @@ +From 5814723c48d41b2675a769850ea7edbecfd917db Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Mon, 13 Jul 2026 01:17:09 +0300 +Subject: wifi: mwifiex: fix freeze for 60 seconds caused by request_firmware + +From: Georgi Valkov + +[ Upstream commit 121a96c5a0db8d18e2ba2cb89660cca8a40508fe ] + +Fix regression in rgpower table loading, caused by using +request_firmware(): when the requested firmware does not exist, e.g. +nxp/rgpower_WW.bin does not exist on OpenWRT builds for WRT3200ACM, +request_firmware() falls back to firmware_fallback_sysfs(), which expects +the firmware to be provided by user space using SYSFS. No such utility is +provided in this configuration, so the entire system locks up for 60 +seconds, until the request times out. During this time, no other log +messages are observed, and the device does not respond to commands over +UART. + +The request_firmware() call is performed in the following context: +current->comm kworker/1:2 in_task 1 irqs_disabled 0 in_atomic 0 + +Fixed by using request_firmware_direct(). This prevents fallback to SYSFS, +and avoids delay. The rgpower table is optional. The driver falls back +to the device tree power table if the firmware is not present. + +The error code is printed for debugging and returned to the caller, +which only cares for success or failure, so there are no side effects. + +Fixes: 7b6f16a25806 ("wifi: mwifiex: add rgpower table loading support") +Signed-off-by: Georgi Valkov +Reviewed-by: Francesco Dolcini +Link: https://patch.msgid.link/20260712221709.7099-1-gvalkov@gmail.com +Signed-off-by: Johannes Berg +Signed-off-by: Sasha Levin +--- + drivers/net/wireless/marvell/mwifiex/sta_ioctl.c | 14 ++++++++------ + 1 file changed, 8 insertions(+), 6 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/sta_ioctl.c b/drivers/net/wireless/marvell/mwifiex/sta_ioctl.c +index a6550548d3b435..9460d5352b234e 100644 +--- a/drivers/net/wireless/marvell/mwifiex/sta_ioctl.c ++++ b/drivers/net/wireless/marvell/mwifiex/sta_ioctl.c +@@ -196,6 +196,7 @@ static int mwifiex_request_rgpower_table(struct mwifiex_private *priv) + struct mwifiex_adapter *adapter = priv->adapter; + char rgpower_table_name[30]; + char country_code[3]; ++ int ret; + + strscpy(country_code, domain_info->country_code, sizeof(country_code)); + +@@ -214,16 +215,17 @@ static int mwifiex_request_rgpower_table(struct mwifiex_private *priv) + adapter->rgpower_data = NULL; + } + +- if ((request_firmware(&adapter->rgpower_data, rgpower_table_name, +- adapter->dev))) { ++ ret = request_firmware_direct(&adapter->rgpower_data, rgpower_table_name, ++ adapter->dev); ++ ++ if (ret) { + mwifiex_dbg( + adapter, INFO, +- "info: %s: failed to request regulatory power table\n", +- __func__); +- return -EIO; ++ "info: %s: failed to request regulatory power table: %d\n", ++ __func__, ret); + } + +- return 0; ++ return ret; + } + + static int mwifiex_dnld_rgpower_table(struct mwifiex_private *priv) +-- +2.53.0 +