A single scheduler fix plugging a race between sched_setscheduler() and

balance_push(). sched_setscheduler() spliced the balance callbacks accross
 a lock break which makes an interleaving schedule() observe an empty list.
 -----BEGIN PGP SIGNATURE-----
 
 iQJHBAABCgAxFiEEQp8+kY+LLUocC4bMphj1TA10mKEFAmKvHpETHHRnbHhAbGlu
 dXRyb25peC5kZQAKCRCmGPVMDXSYoUCfD/9LVsemdRGoz2Ur7LUjKMq9op7rzFmD
 arDTPsvb5ukbD2u+PrObo6pkAQvmSu/b/qFhLQNkGLUMWn/Ok5mq2jikwArboT3X
 k/BC56p7NSynJr2Xb035UcvlnLZxPn0giUo9SeGaGM4zm4rioXyrBWkmsjXmFh19
 LrRTCip9Aom5F+ctCC96BOGvrsM/C3F4si7ZwEp8ZCcClmhrrZhTBEPFGKg0AuVJ
 qJkxgVmvuszaS7CL5ZyHiE35liyqRYx/NLldVWWFKxQcBqlGxqB/4ymB3QyecJfI
 VsuWr8oNZOGbjbhg7Nv/qdbDTz5dHxpEuzHx0osAFhUwQ7TSmJfh2InE1Xe0jNyU
 0HXFsx2kmytLoMDuZCkzGJRaGotSsLL7ZTs2e6Ktbh4j42MGQaHYC5GbX68Doxv1
 A9aoKenh658oU26kPXGI0iYo9tkToMY7U8HM56CXxaAc5+XwHKPoU2pUOUTrD0a/
 eqP2Uu7QMuNzQ6M9vsD/lAm8HUlicp08h9s6wzENIwGSkJ7GdpItat5Mbh1pu7yh
 dL1+1q6kZCBjc9oy7THV4Oqph6Hxgg/oze9Oi9f4ocvt97GHtn1ahEOxey2i/CJR
 7912p/yxbVXWOmUV6BGZ1TxjV3P9HpgWD5KPt/vOItnhMeWFU0NnxW6e0JJSV4T9
 5dmPK8wH0/Nzvg==
 =k54w
 -----END PGP SIGNATURE-----

Merge tag 'sched-urgent-2022-06-19' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip

Pull scheduler fix from Thomas Gleixner:
 "A single scheduler fix plugging a race between sched_setscheduler()
  and balance_push().

  sched_setscheduler() spliced the balance callbacks accross a lock
  break which makes it possible for an interleaving schedule() to
  observe an empty list"

* tag 'sched-urgent-2022-06-19' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip:
  sched: Fix balance_push() vs __sched_setscheduler()
This commit is contained in:
Linus Torvalds 2022-06-19 09:51:00 -05:00
commit 727c3991df
2 changed files with 38 additions and 3 deletions

View file

@ -4798,25 +4798,55 @@ static void do_balance_callbacks(struct rq *rq, struct callback_head *head)
static void balance_push(struct rq *rq); static void balance_push(struct rq *rq);
/*
* balance_push_callback is a right abuse of the callback interface and plays
* by significantly different rules.
*
* Where the normal balance_callback's purpose is to be ran in the same context
* that queued it (only later, when it's safe to drop rq->lock again),
* balance_push_callback is specifically targeted at __schedule().
*
* This abuse is tolerated because it places all the unlikely/odd cases behind
* a single test, namely: rq->balance_callback == NULL.
*/
struct callback_head balance_push_callback = { struct callback_head balance_push_callback = {
.next = NULL, .next = NULL,
.func = (void (*)(struct callback_head *))balance_push, .func = (void (*)(struct callback_head *))balance_push,
}; };
static inline struct callback_head *splice_balance_callbacks(struct rq *rq) static inline struct callback_head *
__splice_balance_callbacks(struct rq *rq, bool split)
{ {
struct callback_head *head = rq->balance_callback; struct callback_head *head = rq->balance_callback;
if (likely(!head))
return NULL;
lockdep_assert_rq_held(rq); lockdep_assert_rq_held(rq);
if (head) /*
* Must not take balance_push_callback off the list when
* splice_balance_callbacks() and balance_callbacks() are not
* in the same rq->lock section.
*
* In that case it would be possible for __schedule() to interleave
* and observe the list empty.
*/
if (split && head == &balance_push_callback)
head = NULL;
else
rq->balance_callback = NULL; rq->balance_callback = NULL;
return head; return head;
} }
static inline struct callback_head *splice_balance_callbacks(struct rq *rq)
{
return __splice_balance_callbacks(rq, true);
}
static void __balance_callbacks(struct rq *rq) static void __balance_callbacks(struct rq *rq)
{ {
do_balance_callbacks(rq, splice_balance_callbacks(rq)); do_balance_callbacks(rq, __splice_balance_callbacks(rq, false));
} }
static inline void balance_callbacks(struct rq *rq, struct callback_head *head) static inline void balance_callbacks(struct rq *rq, struct callback_head *head)

View file

@ -1693,6 +1693,11 @@ queue_balance_callback(struct rq *rq,
{ {
lockdep_assert_rq_held(rq); lockdep_assert_rq_held(rq);
/*
* Don't (re)queue an already queued item; nor queue anything when
* balance_push() is active, see the comment with
* balance_push_callback.
*/
if (unlikely(head->next || rq->balance_callback == &balance_push_callback)) if (unlikely(head->next || rq->balance_callback == &balance_push_callback))
return; return;