All of lore.kernel.org
 help / color / mirror / Atom feed
From: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
To: Eric Dumazet <eric.dumazet@gmail.com>
Cc: "Theodore Ts'o" <tytso@mit.edu>,
	linux-kernel@vger.kernel.org, Matt Mackall <mpm@selenic.com>,
	Greg Kroah-Hartman <greg@kroah.com>
Subject: Re: [PATCH] char random: fix boot id uniqueness race (v2)
Date: Wed, 15 Feb 2012 08:35:06 -0500	[thread overview]
Message-ID: <20120215133505.GA3659@Krystal> (raw)
In-Reply-To: <1329284916.2555.34.camel@edumazet-laptop>

* Eric Dumazet (eric.dumazet@gmail.com) wrote:
> Le mardi 14 février 2012 à 23:10 -0500, Mathieu Desnoyers a écrit :
> > The proc file /proc/sys/kernel/random/boot_id can be read concurrently
> > by user-space processes. If two (or more) user-space processes
> > concurrently read boot_id when sysctl_bootid is not yet assigned, a race
> > can occur making boot_id differ between the reads. Because the whole
> > point of the boot id is to be unique across a kernel execution, fix this
> > by protecting this operation with a mutex, and introduce a
> > boot_id_generated flag, along with appropriate memory barriers, to let
> > the fast-path know if the boot ID has been generated without having to
> > hold the mutex.
> > 
> > I propose this approach rather than setting it up within an initcall(),
> > because letting execution randomness add to entropy before populating
> > the boot id seems to be a wanted property. Also, populating it lazily
> > rather than at boot time only makes the performance hit be taken when
> > boot_id is being read.
> > 
> > 
> > Q: Why are these memory barriers required ? Aren't the mutexes already
> >    dealing with ordering ?
> > 
> > The need for memory barriers is a consequence of letting the fast-path
> > run without holding this mutex.
> > 
> > Here is the race dealt with by the smp_rmb()/smp_wmb(). I'm showing the
> > result of reversed write order here:
> > 
> > CPU A                             CPU B
> > 
> > Load boot_id_generated            
> >   (test -> false)
> > mutex_lock(&boot_id_mutex)
> >   (implied memory barrier
> >    with acquire semantic)
> > Load boot_id_generated again
> >    (test -> false)
> > boot_id_generated = 1
> >   (both the compiler and
> >    CPU are free to reorder
> >    the boot_id_generated
> >    store before uuid stores)
> >                                   Load boot_id_generated
> >                                     (test -> true)
> >                                   Load uuid content
> >                                     (races with generate_random_uuid:
> >                                      result either 0 or corrupted)
> >                                   Return corrupted uuid.
> > generate_random_uuid(uuid)
> > mutex_unlock(&boot_id_mutex)
> > 
> > I prefer not requiring the fast-path to take a mutex, because this
> > would transform a read-mostly operation into an operation that
> > requires cache-line exchanges (the mutex). However, if we want the
> > fast-path to be mutex-free, we need to enforce order with
> > memory barriers: smp_rmb on the read-side, smp_wmb on the
> > update-side. Failure to do so leads to the race shown above, where
> > a corrupted boot_id can be returned.
> > 
> > 
> > * Changelog since v1:
> > - boot_id_mutex is now declared within the proc_do_uuid scope.
> > - added explanation for memory barriers.
> > 
> > Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
> > CC: "Theodore Ts'o" <tytso@mit.edu>
> > CC: Matt Mackall <mpm@selenic.com>
> > CC: Greg Kroah-Hartman <greg@kroah.com>
> > ---
> >  drivers/char/random.c |   20 +++++++++++++++++---
> >  1 file changed, 17 insertions(+), 3 deletions(-)
> > 
> > Index: linux-2.6-lttng/drivers/char/random.c
> > ===================================================================
> > --- linux-2.6-lttng.orig/drivers/char/random.c
> > +++ linux-2.6-lttng/drivers/char/random.c
> > @@ -1244,16 +1244,30 @@ static char sysctl_bootid[16];
> >  static int proc_do_uuid(ctl_table *table, int write,
> >  			void __user *buffer, size_t *lenp, loff_t *ppos)
> >  {
> > +	static int boot_id_generated;
> > +	static DEFINE_MUTEX(boot_id_mutex);
> >  	ctl_table fake_table;
> >  	unsigned char buf[64], tmp_uuid[16], *uuid;
> >  
> >  	uuid = table->data;
> >  	if (!uuid) {
> >  		uuid = tmp_uuid;
> > -		uuid[8] = 0;
> > -	}
> > -	if (uuid[8] == 0)
> >  		generate_random_uuid(uuid);
> > +	} else {
> > +		if (unlikely(!ACCESS_ONCE(boot_id_generated))) {
> > +			mutex_lock(&boot_id_mutex);
> > +			if (!boot_id_generated) {
> > +				generate_random_uuid(uuid);
> > +				/* Store uuid before boot_id_generated. */
> > +				smp_wmb();
> > +				boot_id_generated = 1;
> > +			}
> > +			mutex_unlock(&boot_id_mutex);
> > +		} else {
> > +			/* Load boot_id_generated before uuid */
> > +			smp_rmb();
> > +		}
> > +	}
> >  
> >  	sprintf(buf, "%pU", uuid);
> >  
> > 
> 
> This seems overly complex to me.
> 
> I doubt this is performance critical path ?

Fair point, I don't see many use-cases where an application would like
to get the boot_id value (which stays constant over an entire kernel
execution) very frequently, unlike /proc/sys/kernel/random/uuid.

> 
> What about a basic patch like :
> 
> diff --git a/drivers/char/random.c b/drivers/char/random.c
> index 54ca8b2..af6040d 100644
> --- a/drivers/char/random.c
> +++ b/drivers/char/random.c
> @@ -1260,11 +1260,15 @@ static int proc_do_uuid(ctl_table *table, int write,
>  	uuid = table->data;
>  	if (!uuid) {
>  		uuid = tmp_uuid;
> -		uuid[8] = 0;
> -	}
> -	if (uuid[8] == 0)
>  		generate_random_uuid(uuid);
> +	} else {
> +		static DEFINE_SPINLOCK(bootid_spinlock);
>  
> +		spin_lock(&bootid_spinlock);
> +		if (!uuid[8])
> +			generate_random_uuid(uuid);
> +		spin_unlock(&bootid_spinlock);

That would make sense, as long as we're OK about turning a read-mostly
operation into a fully serialized operation that requires to exchange
the lock between processor cache-lines. But as you point out, it should
be fairly unfrequently used.

Any particular reason to use a spin lock rather than a mutex ? I did put
a mutex in my implementation assuming that it would be a little more
RT-friendly.

Thanks,

Mathieu

> +	}
>  	sprintf(buf, "%pU", uuid);
>  
>  	fake_table.data = buf;
> 
> 
> 

-- 
Mathieu Desnoyers
Operating System Efficiency R&D Consultant
EfficiOS Inc.
http://www.efficios.com

  reply	other threads:[~2012-02-15 13:35 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2012-02-15  4:10 [PATCH] char random: fix boot id uniqueness race (v2) Mathieu Desnoyers
2012-02-15  5:48 ` Eric Dumazet
2012-02-15 13:35   ` Mathieu Desnoyers [this message]
2012-02-15 14:08     ` Eric Dumazet

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20120215133505.GA3659@Krystal \
    --to=mathieu.desnoyers@efficios.com \
    --cc=eric.dumazet@gmail.com \
    --cc=greg@kroah.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mpm@selenic.com \
    --cc=tytso@mit.edu \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.