Skip to content

Commit 45ece99

Browse files
edumazetKAGA-KOKO
authored andcommitted
posix-timers: Initialise timer before adding it to the hash table
A timer is only valid in the hashtable when both timer::it_signal and timer::it_id are set to their final values, but timers are added without those values being set. The timer ID is allocated when the timer is added to the hash in invalid state. The ID is taken from a monotonically increasing per process counter which wraps around after reaching INT_MAX. The hash insertion validates that there is no timer with the allocated ID in the hash table which belongs to the same process. That opens a mostly theoretical race condition: If other threads of the same process manage to create/delete timers in rapid succession before the newly created timer is fully initialized and wrap around to the timer ID which was handed out, then a duplicate timer ID will be inserted into the hash table. Prevent this by: 1) Setting timer::it_id before inserting the timer into the hashtable. 2) Storing the signal pointer in timer::it_signal with bit 0 set before inserting it into the hashtable. Bit 0 acts as a invalid bit, which means that the regular lookup for sys_timer_*() will fail the comparison with the signal pointer. But the lookup on insertion masks out bit 0 and can therefore detect a timer which is not yet valid, but allocated in the hash table. Bit 0 in the pointer is cleared once the initialization of the timer completed. [ tglx: Fold ID and signal iniitializaion into one patch and massage change log and comments. ] Signed-off-by: Eric Dumazet <[email protected]> Signed-off-by: Thomas Gleixner <[email protected]> Reviewed-by: Frederic Weisbecker <[email protected]> Link: https://lore.kernel.org/all/[email protected] Link: https://lore.kernel.org/all/[email protected]
1 parent 2389c6e commit 45ece99

File tree

1 file changed

+42
-14
lines changed

1 file changed

+42
-14
lines changed

kernel/time/posix-timers.c

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -72,26 +72,40 @@ static int hash(struct signal_struct *sig, unsigned int nr)
7272
return hash_32(hash32_ptr(sig) ^ nr, HASH_BITS(posix_timers_hashtable));
7373
}
7474

75-
static struct k_itimer *__posix_timers_find(struct hlist_head *head,
76-
struct signal_struct *sig,
77-
timer_t id)
75+
static struct k_itimer *posix_timer_by_id(timer_t id)
7876
{
77+
struct signal_struct *sig = current->signal;
78+
struct hlist_head *head = &posix_timers_hashtable[hash(sig, id)];
7979
struct k_itimer *timer;
8080

81-
hlist_for_each_entry_rcu(timer, head, t_hash, lockdep_is_held(&hash_lock)) {
81+
hlist_for_each_entry_rcu(timer, head, t_hash) {
8282
/* timer->it_signal can be set concurrently */
8383
if ((READ_ONCE(timer->it_signal) == sig) && (timer->it_id == id))
8484
return timer;
8585
}
8686
return NULL;
8787
}
8888

89-
static struct k_itimer *posix_timer_by_id(timer_t id)
89+
static inline struct signal_struct *posix_sig_owner(const struct k_itimer *timer)
9090
{
91-
struct signal_struct *sig = current->signal;
92-
struct hlist_head *head = &posix_timers_hashtable[hash(sig, id)];
91+
unsigned long val = (unsigned long)timer->it_signal;
92+
93+
/*
94+
* Mask out bit 0, which acts as invalid marker to prevent
95+
* posix_timer_by_id() detecting it as valid.
96+
*/
97+
return (struct signal_struct *)(val & ~1UL);
98+
}
99+
100+
static bool posix_timer_hashed(struct hlist_head *head, struct signal_struct *sig, timer_t id)
101+
{
102+
struct k_itimer *timer;
93103

94-
return __posix_timers_find(head, sig, id);
104+
hlist_for_each_entry_rcu(timer, head, t_hash, lockdep_is_held(&hash_lock)) {
105+
if ((posix_sig_owner(timer) == sig) && (timer->it_id == id))
106+
return true;
107+
}
108+
return false;
95109
}
96110

97111
static int posix_timer_add(struct k_itimer *timer)
@@ -112,7 +126,19 @@ static int posix_timer_add(struct k_itimer *timer)
112126
sig->next_posix_timer_id = (id + 1) & INT_MAX;
113127

114128
head = &posix_timers_hashtable[hash(sig, id)];
115-
if (!__posix_timers_find(head, sig, id)) {
129+
if (!posix_timer_hashed(head, sig, id)) {
130+
/*
131+
* Set the timer ID and the signal pointer to make
132+
* it identifiable in the hash table. The signal
133+
* pointer has bit 0 set to indicate that it is not
134+
* yet fully initialized. posix_timer_hashed()
135+
* masks this bit out, but the syscall lookup fails
136+
* to match due to it being set. This guarantees
137+
* that there can't be duplicate timer IDs handed
138+
* out.
139+
*/
140+
timer->it_id = (timer_t)id;
141+
timer->it_signal = (struct signal_struct *)((unsigned long)sig | 1UL);
116142
hlist_add_head_rcu(&timer->t_hash, head);
117143
spin_unlock(&hash_lock);
118144
return id;
@@ -406,16 +432,14 @@ static int do_timer_create(clockid_t which_clock, struct sigevent *event,
406432

407433
/*
408434
* Add the timer to the hash table. The timer is not yet valid
409-
* because new_timer::it_signal is still NULL. The timer id is also
410-
* not yet visible to user space.
435+
* after insertion, but has a unique ID allocated.
411436
*/
412437
new_timer_id = posix_timer_add(new_timer);
413438
if (new_timer_id < 0) {
414439
posixtimer_free_timer(new_timer);
415440
return new_timer_id;
416441
}
417442

418-
new_timer->it_id = (timer_t) new_timer_id;
419443
new_timer->it_clock = which_clock;
420444
new_timer->kclock = kc;
421445
new_timer->it_overrun = -1LL;
@@ -453,7 +477,7 @@ static int do_timer_create(clockid_t which_clock, struct sigevent *event,
453477
}
454478
/*
455479
* After succesful copy out, the timer ID is visible to user space
456-
* now but not yet valid because new_timer::signal is still NULL.
480+
* now but not yet valid because new_timer::signal low order bit is 1.
457481
*
458482
* Complete the initialization with the clock specific create
459483
* callback.
@@ -470,7 +494,11 @@ static int do_timer_create(clockid_t which_clock, struct sigevent *event,
470494
*/
471495
scoped_guard (spinlock_irq, &new_timer->it_lock) {
472496
guard(spinlock)(&current->sighand->siglock);
473-
/* This makes the timer valid in the hash table */
497+
/*
498+
* new_timer::it_signal contains the signal pointer with
499+
* bit 0 set, which makes it invalid for syscall operations.
500+
* Store the unmodified signal pointer to make it valid.
501+
*/
474502
WRITE_ONCE(new_timer->it_signal, current->signal);
475503
hlist_add_head(&new_timer->list, &current->signal->posix_timers);
476504
}

0 commit comments

Comments
 (0)