[PATCH v4] lib/vsprintf: defer filling siphash key on RT

Jason A. Donenfeld posted 1 patch 3 years, 4 months ago
There is a newer version of this series
lib/vsprintf.c | 35 ++++++++++++++++++++---------------
1 file changed, 20 insertions(+), 15 deletions(-)
[PATCH v4] lib/vsprintf: defer filling siphash key on RT
Posted by Jason A. Donenfeld 3 years, 4 months ago
On RT, we can't call get_random_bytes() from inside of the raw locks
that callers of vsprintf might take, because get_random_bytes() takes
normal spinlocks. So on those RT systems, defer the siphash key
generation to a worker.

Also, avoid using a static_branch, as this isn't the fast path.
Using static_branch_likely() to signal that ptr_key has been filled is a
bit much given that it is not a fast path.

Cc: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Reported-by: Mike Galbraith <efault@gmx.de>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
---
Sebastian - feel free to take this and tweak it as needed. Sending this
mostly as something illustrative of what the "simpler" thing would be
that I had in mind. -Jason

 lib/vsprintf.c | 35 ++++++++++++++++++++---------------
 1 file changed, 20 insertions(+), 15 deletions(-)

diff --git a/lib/vsprintf.c b/lib/vsprintf.c
index 3c1853a9d1c0..5a67f6f65ddc 100644
--- a/lib/vsprintf.c
+++ b/lib/vsprintf.c
@@ -750,37 +750,42 @@ static int __init debug_boot_weak_hash_enable(char *str)
 }
 early_param("debug_boot_weak_hash", debug_boot_weak_hash_enable);
 
-static DEFINE_STATIC_KEY_FALSE(filled_random_ptr_key);
+static bool filled_ptr_key __read_mostly;
+static siphash_key_t ptr_key __read_mostly;
 
-static void enable_ptr_key_workfn(struct work_struct *work)
+static void fill_ptr_key_workfn(struct work_struct *work)
 {
-	static_branch_enable(&filled_random_ptr_key);
+	if (READ_ONCE(filled_ptr_key))
+		return;
+	get_random_bytes(&ptr_key, sizeof(ptr_key));
+	/* Pairs with smp_rmb() before reading ptr_key. */
+	smp_wmb();
+	WRITE_ONCE(filled_ptr_key, true);
 }
 
 /* Maps a pointer to a 32 bit unique identifier. */
 static inline int __ptr_to_hashval(const void *ptr, unsigned long *hashval_out)
 {
-	static siphash_key_t ptr_key __read_mostly;
 	unsigned long hashval;
 
-	if (!static_branch_likely(&filled_random_ptr_key)) {
-		static bool filled = false;
+	if (!READ_ONCE(filled_ptr_key)) {
 		static DEFINE_SPINLOCK(filling);
-		static DECLARE_WORK(enable_ptr_key_work, enable_ptr_key_workfn);
 		unsigned long flags;
 
-		if (!system_unbound_wq || !rng_is_initialized() ||
-		    !spin_trylock_irqsave(&filling, flags))
+		if (IS_ENABLED(CONFIG_PREEMPT_RT) && rng_is_initialized()) {
+			static DECLARE_WORK(fill_ptr_key_work, fill_ptr_key_workfn);
+			queue_work(system_unbound_wq, &fill_ptr_key_work);
 			return -EAGAIN;
-
-		if (!filled) {
-			get_random_bytes(&ptr_key, sizeof(ptr_key));
-			queue_work(system_unbound_wq, &enable_ptr_key_work);
-			filled = true;
 		}
+
+		if (!rng_is_initialized() || !spin_trylock_irqsave(&filling, flags))
+			return -EAGAIN;
+
+		fill_ptr_key_workfn(NULL);
 		spin_unlock_irqrestore(&filling, flags);
 	}
-
+	/* Pairs with smp_wmb() after writing ptr_key. */
+	smp_rmb();
 
 #ifdef CONFIG_64BIT
 	hashval = (unsigned long)siphash_1u64((u64)ptr, &ptr_key);
-- 
2.35.1
Re: [PATCH v4] lib/vsprintf: defer filling siphash key on RT
Posted by Sebastian Andrzej Siewior 3 years, 4 months ago
On 2022-08-01 14:39:46 [+0200], Jason A. Donenfeld wrote:
> On RT, we can't call get_random_bytes() from inside of the raw locks
> that callers of vsprintf might take, because get_random_bytes() takes
> normal spinlocks. So on those RT systems, defer the siphash key
> generation to a worker.
> 
> Also, avoid using a static_branch, as this isn't the fast path.
> Using static_branch_likely() to signal that ptr_key has been filled is a
> bit much given that it is not a fast path.
> 
> Cc: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
> Reported-by: Mike Galbraith <efault@gmx.de>
> Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
> ---
> Sebastian - feel free to take this and tweak it as needed. Sending this
> mostly as something illustrative of what the "simpler" thing would be
> that I had in mind. -Jason

Can have the same behaviour regardless of CONFIG_PREEMPT_RT? Here
lockdep _may_ yell with !RT because it is broken for RT.
If we agree that we drop the first %p print here, can we do this on
both (regardless of CONFIG_PREEMPT_RT)?

Sebastian
Re: [PATCH v4] lib/vsprintf: defer filling siphash key on RT
Posted by Jason A. Donenfeld 3 years, 4 months ago
Hi Sebastian,

On Mon, Aug 01, 2022 at 02:46:35PM +0200, Sebastian Andrzej Siewior wrote:
> On 2022-08-01 14:39:46 [+0200], Jason A. Donenfeld wrote:
> > On RT, we can't call get_random_bytes() from inside of the raw locks
> > that callers of vsprintf might take, because get_random_bytes() takes
> > normal spinlocks. So on those RT systems, defer the siphash key
> > generation to a worker.
> > 
> > Also, avoid using a static_branch, as this isn't the fast path.
> > Using static_branch_likely() to signal that ptr_key has been filled is a
> > bit much given that it is not a fast path.
> > 
> > Cc: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
> > Reported-by: Mike Galbraith <efault@gmx.de>
> > Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
> > ---
> > Sebastian - feel free to take this and tweak it as needed. Sending this
> > mostly as something illustrative of what the "simpler" thing would be
> > that I had in mind. -Jason
> 
> Can have the same behaviour regardless of CONFIG_PREEMPT_RT? Here
> lockdep _may_ yell with !RT because it is broken for RT.
> If we agree that we drop the first %p print here, can we do this on
> both (regardless of CONFIG_PREEMPT_RT)?

"Lockdep may yell" -- but this would be when lockdep is turned on to
catch RT bugs, not to catch non-RT bugs. The actual bug only exists on
RT. This is an RT problem. Stop pretending that this is a real issue
outside of RT. It isn't. This is *only* an RT issue. So why would we
make things worse for an issue that doesn't actually exist on non-RT?

I too generally prefer having only one code path and not two. But the
way this patch is written, the worker function just gets reused with a
straight call on the non-RT case, so it doesn't actually require
duplicating code.

Jason
Re: [PATCH v4] lib/vsprintf: defer filling siphash key on RT
Posted by Sebastian Andrzej Siewior 3 years, 4 months ago
On 2022-08-01 15:36:32 [+0200], Jason A. Donenfeld wrote:
> Hi Sebastian,
Hi Jason,

> > Can have the same behaviour regardless of CONFIG_PREEMPT_RT? Here
> > lockdep _may_ yell with !RT because it is broken for RT.
> > If we agree that we drop the first %p print here, can we do this on
> > both (regardless of CONFIG_PREEMPT_RT)?
> 
> "Lockdep may yell" -- but this would be when lockdep is turned on to
> catch RT bugs, not to catch non-RT bugs. The actual bug only exists on
> RT. This is an RT problem. Stop pretending that this is a real issue
> outside of RT. It isn't. This is *only* an RT issue. So why would we
> make things worse for an issue that doesn't actually exist on non-RT?

You do remember the warning that poped up in random core with
CONFIG_PROVE_RAW_LOCK_NESTING enabled? Where I said it does not affect
!RT it just points out a RT problem in a !RT config?
If you fix this with one code path for RT and another one for !RT then
you will have this warning _if_ the caller has a raw_spinlock_t
acquired.

> I too generally prefer having only one code path and not two. But the
> way this patch is written, the worker function just gets reused with a
> straight call on the non-RT case, so it doesn't actually require
> duplicating code.

> Jason

Sebastian
Re: [PATCH v4] lib/vsprintf: defer filling siphash key on RT
Posted by Jason A. Donenfeld 3 years, 4 months ago
Hi Sebastian,

On 8/1/22, Sebastian Andrzej Siewior <bigeasy@linutronix.de> wrote:
> You do remember the warning that poped up in random core with
> CONFIG_PROVE_RAW_LOCK_NESTING enabled? Where I said it does not affect
> !RT it just points out a RT problem in a !RT config?
> If you fix this with one code path for RT and another one for !RT then
> you will have this warning _if_ the caller has a raw_spinlock_t
> acquired.

CONFIG_PROVE_RAW_LOCK_NESTING catches RT issues on non-RT. It doesn't
catch non-RT issues. So lockdep isn't actually warning about something
real as far as non-RT is concerned when CONFIG_PROVE_RAW_LOCK_NESTING
is enabled.

So probably this v4 patch should expand that condition to be:

IS_ENABLED(CONFIG_PROVE_RAW_LOCK_NESTING) || IS_ENABLED(CONFIG_PREEMPT_RT)

That's somewhat distasteful, I realize, but it *does* make the code
actually match reality, which is maybe better.

Jason
[PATCH v5] lib/vsprintf: defer filling siphash key on RT
Posted by Jason A. Donenfeld 3 years, 4 months ago
On RT, we can't call get_random_bytes() from inside of the raw locks
that callers of vsprintf might take, because get_random_bytes() takes
normal spinlocks. So on those RT systems, defer the siphash key
generation to a worker.

We also do the deferal for CONFIG_PROVE_RAW_LOCK_NESTING systems, which
catches RT issues on non-RT. Branching on CONFIG_PROVE_RAW_LOCK_NESTING
is partly awful, as it basically defeats the purpose of lockdep. But in
this case, it really generates incorrect splats.

Also, avoid using a static_branch, as this isn't the fast path.

Cc: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Reported-by: Mike Galbraith <efault@gmx.de>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
---
Sebastian - again, feel free to take this and modify it as needed. Just
posting ideas... -Jason

 lib/vsprintf.c | 36 +++++++++++++++++++++---------------
 1 file changed, 21 insertions(+), 15 deletions(-)

diff --git a/lib/vsprintf.c b/lib/vsprintf.c
index 3c1853a9d1c0..a2a61915eb6b 100644
--- a/lib/vsprintf.c
+++ b/lib/vsprintf.c
@@ -750,37 +750,43 @@ static int __init debug_boot_weak_hash_enable(char *str)
 }
 early_param("debug_boot_weak_hash", debug_boot_weak_hash_enable);
 
-static DEFINE_STATIC_KEY_FALSE(filled_random_ptr_key);
+static bool filled_ptr_key __read_mostly;
+static siphash_key_t ptr_key __read_mostly;
 
-static void enable_ptr_key_workfn(struct work_struct *work)
+static void fill_ptr_key_workfn(struct work_struct *work)
 {
-	static_branch_enable(&filled_random_ptr_key);
+	if (READ_ONCE(filled_ptr_key))
+		return;
+	get_random_bytes(&ptr_key, sizeof(ptr_key));
+	/* Pairs with smp_rmb() before reading ptr_key. */
+	smp_wmb();
+	WRITE_ONCE(filled_ptr_key, true);
 }
 
 /* Maps a pointer to a 32 bit unique identifier. */
 static inline int __ptr_to_hashval(const void *ptr, unsigned long *hashval_out)
 {
-	static siphash_key_t ptr_key __read_mostly;
 	unsigned long hashval;
 
-	if (!static_branch_likely(&filled_random_ptr_key)) {
-		static bool filled = false;
+	if (!READ_ONCE(filled_ptr_key)) {
 		static DEFINE_SPINLOCK(filling);
-		static DECLARE_WORK(enable_ptr_key_work, enable_ptr_key_workfn);
 		unsigned long flags;
 
-		if (!system_unbound_wq || !rng_is_initialized() ||
-		    !spin_trylock_irqsave(&filling, flags))
+		if ((IS_ENABLED(CONFIG_PREEMPT_RT) || IS_ENABLED(CONFIG_PROVE_RAW_LOCK_NESTING)) &&
+		    rng_is_initialized()) {
+			static DECLARE_WORK(fill_ptr_key_work, fill_ptr_key_workfn);
+			queue_work(system_unbound_wq, &fill_ptr_key_work);
 			return -EAGAIN;
-
-		if (!filled) {
-			get_random_bytes(&ptr_key, sizeof(ptr_key));
-			queue_work(system_unbound_wq, &enable_ptr_key_work);
-			filled = true;
 		}
+
+		if (!rng_is_initialized() || !spin_trylock_irqsave(&filling, flags))
+			return -EAGAIN;
+
+		fill_ptr_key_workfn(NULL);
 		spin_unlock_irqrestore(&filling, flags);
 	}
-
+	/* Pairs with smp_wmb() after writing ptr_key. */
+	smp_rmb();
 
 #ifdef CONFIG_64BIT
 	hashval = (unsigned long)siphash_1u64((u64)ptr, &ptr_key);
-- 
2.35.1
Re: [PATCH v5] lib/vsprintf: defer filling siphash key on RT
Posted by Sebastian Andrzej Siewior 3 years, 4 months ago
On 2022-08-01 16:12:45 [+0200], Jason A. Donenfeld wrote:
> Sebastian - again, feel free to take this and modify it as needed. Just
> posting ideas... -Jason

The plan is to make CONFIG_PROVE_RAW_LOCK_NESTING part of LOCKDEP once
the issues have been sorted out.

Sebastian
Re: [PATCH v4] lib/vsprintf: defer filling siphash key on RT
Posted by Jason A. Donenfeld 3 years, 4 months ago
Hey again,

On Mon, Aug 01, 2022 at 03:36:32PM +0200, Jason A. Donenfeld wrote:
> Hi Sebastian,
> 
> On Mon, Aug 01, 2022 at 02:46:35PM +0200, Sebastian Andrzej Siewior wrote:
> > On 2022-08-01 14:39:46 [+0200], Jason A. Donenfeld wrote:
> > > On RT, we can't call get_random_bytes() from inside of the raw locks
> > > that callers of vsprintf might take, because get_random_bytes() takes
> > > normal spinlocks. So on those RT systems, defer the siphash key
> > > generation to a worker.
> > > 
> > > Also, avoid using a static_branch, as this isn't the fast path.
> > > Using static_branch_likely() to signal that ptr_key has been filled is a
> > > bit much given that it is not a fast path.
> > > 
> > > Cc: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
> > > Reported-by: Mike Galbraith <efault@gmx.de>
> > > Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
> > > ---
> > > Sebastian - feel free to take this and tweak it as needed. Sending this
> > > mostly as something illustrative of what the "simpler" thing would be
> > > that I had in mind. -Jason
> > 
> > Can have the same behaviour regardless of CONFIG_PREEMPT_RT? Here
> > lockdep _may_ yell with !RT because it is broken for RT.
> > If we agree that we drop the first %p print here, can we do this on
> > both (regardless of CONFIG_PREEMPT_RT)?
> 
> "Lockdep may yell" -- but this would be when lockdep is turned on to
> catch RT bugs, not to catch non-RT bugs. The actual bug only exists on
> RT. This is an RT problem. Stop pretending that this is a real issue
> outside of RT. It isn't. This is *only* an RT issue. So why would we
> make things worse for an issue that doesn't actually exist on non-RT?
> 
> I too generally prefer having only one code path and not two. But the
> way this patch is written, the worker function just gets reused with a
> straight call on the non-RT case, so it doesn't actually require
> duplicating code.
> 
> Jason

By the way, another option that would be fine with me would be to make
random.c use all raw spinlocks. From a non-RT perspective, that wouldn't
change the codegen at all, so it doesn't make a huge difference to me.
From an RT perspective, it would presumably fix a lot of these issues,
and enable randomness to be available in any context, which is maybe
what we want anyway. From an RT-safety point of view, I suspect doing
this might actually be okay, because the locks are only ever protecting
operations that are fixed duration CPU-bound, like generating a chacha
block or something, not waiting for some I/O.

Thoughts on that?

Jason
Re: [PATCH v4] lib/vsprintf: defer filling siphash key on RT
Posted by Sebastian Andrzej Siewior 3 years, 4 months ago
On 2022-08-01 15:44:12 [+0200], Jason A. Donenfeld wrote:
> Hey again,
Hi Jason,

> By the way, another option that would be fine with me would be to make
> random.c use all raw spinlocks. From a non-RT perspective, that wouldn't
> change the codegen at all, so it doesn't make a huge difference to me.
> From an RT perspective, it would presumably fix a lot of these issues,
> and enable randomness to be available in any context, which is maybe
> what we want anyway. From an RT-safety point of view, I suspect doing
> this might actually be okay, because the locks are only ever protecting
> operations that are fixed duration CPU-bound, like generating a chacha
> block or something, not waiting for some I/O.
> 
> Thoughts on that?

That random-core change regarding random numbers broke lockdep, kasan (I
think) and now printk's %p. Each one of them appears to be exceptional
since we don't have _that_ many users asking for random numbers in
atomic context.
Making the locks raw would indeed solve all the issues at once. Last
time I was looking into this, would include three locks and I tried to
trigger the worst-case via "re-seed" and this was visible back then.
After the rework you did back thinks looked good.

> Jason

Sebastian
Re: [PATCH v4] lib/vsprintf: defer filling siphash key on RT
Posted by Jason A. Donenfeld 3 years, 4 months ago
Hi Sebastian,

On Mon, Aug 1, 2022 at 4:25 PM Sebastian Andrzej Siewior
<bigeasy@linutronix.de> wrote:
>
> On 2022-08-01 15:44:12 [+0200], Jason A. Donenfeld wrote:
> > Hey again,
> Hi Jason,
>
> > By the way, another option that would be fine with me would be to make
> > random.c use all raw spinlocks. From a non-RT perspective, that wouldn't
> > change the codegen at all, so it doesn't make a huge difference to me.
> > From an RT perspective, it would presumably fix a lot of these issues,
> > and enable randomness to be available in any context, which is maybe
> > what we want anyway. From an RT-safety point of view, I suspect doing
> > this might actually be okay, because the locks are only ever protecting
> > operations that are fixed duration CPU-bound, like generating a chacha
> > block or something, not waiting for some I/O.
> >
> > Thoughts on that?
>
> That random-core change regarding random numbers broke lockdep, kasan (I
> think) and now printk's %p. Each one of them appears to be exceptional
> since we don't have _that_ many users asking for random numbers in
> atomic context.

Actually, the printk %p case was caused by something different than
the other. This used to be initialized with a clunky notifier callback
mechanism, which I got rid of, replacing it with this direct thing.
It's this direct thing that's now causing problems on RT.

> Making the locks raw would indeed solve all the issues at once. Last
> time I was looking into this, would include three locks and I tried to
> trigger the worst-case via "re-seed" and this was visible back then.
> After the rework you did back thinks looked good.

I actually just sent a patch for that to you a second ago:
https://lore.kernel.org/lkml/20220801142530.133007-1-Jason@zx2c4.com/

It's only two locks, and usage seems pretty constrained in a good way.
So okay, if you're on board, let's just do that, and then printk can
stay the same.

Jason