From c12c2d4488782d19f4f91560dc6828bb2fc1a548 Mon Sep 17 00:00:00 2001 From: Augustin Cavalier Date: Thu, 13 Aug 2026 14:55:30 -0400 Subject: [PATCH] kernel/thread: Fix behavior of {get|set}_thread_affinity. * Handle threads without masks: this means they can run on any CPU. * Return only enabled CPUs from get_thread_affinity. * Clear the user mask's extra bits. * Allow masks with more CPUs set than are active, same as Linux, but disallow masks with no intersection with the active CPUs. * If setting to all enabled CPUs, clear the mask, so that if more CPUs are enabled in the future, this can run on them too. * Add permissions checks. Fixes #20251. (cherry picked from commit f46b3483057b149179dda7abdcc2dde38994a8cc) Change-Id: Ib8649cb0a252fc8d9cfca691c2e03748561db741 Reviewed-on: https://review.haiku-os.org/c/haiku/+/11529 Reviewed-by: waddlesplash --- headers/private/kernel/smp.h | 14 ++++++++ src/system/kernel/thread.cpp | 69 +++++++++++++++++++++++------------- 2 files changed, 59 insertions(+), 24 deletions(-) diff --git a/headers/private/kernel/smp.h b/headers/private/kernel/smp.h index 9869daa871..4e2560f80b 100644 --- a/headers/private/kernel/smp.h +++ b/headers/private/kernel/smp.h @@ -58,10 +58,12 @@ public: inline bool Matches(const CPUSet& mask) const; inline CPUSet And(const CPUSet& mask) const; + inline bool operator==(const CPUSet& other) const; inline bool IsEmpty() const; inline uint32 Bits(uint32 index) const { return fBitmap[index];} + private: static const int kArrayBits = 32; static const int kArraySize = ROUNDUP(SMP_MAX_CPUS, kArrayBits) / kArrayBits; @@ -199,6 +201,18 @@ CPUSet::Matches(const CPUSet& mask) const } +inline bool +CPUSet::operator==(const CPUSet& other) const +{ + for (int i = 0; i < kArraySize; i++) { + if (fBitmap[i] != other.fBitmap[i]) + return false; + } + + return true; +} + + inline bool CPUSet::IsEmpty() const { diff --git a/src/system/kernel/thread.cpp b/src/system/kernel/thread.cpp index c16cc4d9e0..ce0cf974aa 100644 --- a/src/system/kernel/thread.cpp +++ b/src/system/kernel/thread.cpp @@ -4026,28 +4026,43 @@ _user_get_cpu() status_t -_user_get_thread_affinity(thread_id id, void* userMask, size_t size) +_user_get_thread_affinity(thread_id id, void* userMask, size_t userMaskSize) { - if (userMask == NULL || id < B_OK) + const size_t maskSize = sizeof(CPUSet); + if (userMask == NULL || id < 0 || userMaskSize < maskSize) return B_BAD_VALUE; - if (!IS_USER_ADDRESS(userMask)) return B_BAD_ADDRESS; CPUSet mask; - if (id == 0) - id = thread_get_current_thread_id(); - // get the thread - Thread* thread = Thread::GetAndLock(id); - if (thread == NULL) - return B_BAD_THREAD_ID; - BReference threadReference(thread, true); - ThreadLocker threadLocker(thread, true); - memcpy(&mask, &thread->cpumask, sizeof(mask)); + { + if (id == 0) + id = thread_get_current_thread_id(); + Thread* thread = Thread::GetAndLock(id); + if (thread == NULL) + return B_BAD_THREAD_ID; + BReference threadReference(thread, true); + ThreadLocker threadLocker(thread, true); - if (user_memcpy(userMask, &mask, min_c(sizeof(mask), size)) < B_OK) + if (thread->team == team_get_kernel_team()) + return B_NOT_ALLOWED; + if (thread->team != thread_get_current_thread()->team && geteuid() != 0) + return B_NOT_ALLOWED; + + mask = thread->cpumask; + } + + if (mask.IsEmpty()) + mask.SetAll(); + mask = mask.And(gCPUEnabled); + + if (user_memcpy(userMask, &mask, maskSize) < B_OK) return B_BAD_ADDRESS; + if (userMaskSize > maskSize) { + if (user_memset((uint8*)userMask + maskSize, 0, userMaskSize - maskSize) != B_OK) + return B_BAD_ADDRESS; + } return B_OK; } @@ -4057,34 +4072,40 @@ _user_set_thread_affinity(thread_id id, const void* userMask, size_t size) { if (userMask == NULL || id < B_OK || size < sizeof(CPUSet)) return B_BAD_VALUE; - if (!IS_USER_ADDRESS(userMask)) return B_BAD_ADDRESS; CPUSet mask; - if (user_memcpy(&mask, userMask, min_c(sizeof(CPUSet), size)) < B_OK) + if (user_memcpy(&mask, userMask, sizeof(CPUSet)) < B_OK) return B_BAD_ADDRESS; - CPUSet cpus; - cpus.SetAll(); - for (int i = 0; i < smp_get_num_cpus(); i++) - cpus.ClearBit(i); - if (mask.Matches(cpus)) + CPUSet andEnabled = mask.And(gCPUEnabled); + if (andEnabled.IsEmpty()) return B_BAD_VALUE; + // if setting to all enabled CPUs, just use no mask instead + if (andEnabled == gCPUEnabled) + mask.ClearAll(); + if (id == 0) id = thread_get_current_thread_id(); - // get the thread Thread* thread = Thread::GetAndLock(id); if (thread == NULL) return B_BAD_THREAD_ID; BReference threadReference(thread, true); ThreadLocker threadLocker(thread, true); - memcpy(&thread->cpumask, &mask, sizeof(mask)); - // check if running on masked cpu - if (!thread->cpumask.GetBit(thread->cpu->cpu_num)) + if (thread->team == team_get_kernel_team()) + return B_NOT_ALLOWED; + if (thread->team != thread_get_current_thread()->team && geteuid() != 0) + return B_NOT_ALLOWED; + + thread->cpumask = mask; + threadLocker.Unlock(); + + // check if running on masked CPU + if (!mask.IsEmpty() && !mask.GetBit(thread->cpu->cpu_num)) thread_yield(); return B_OK;