On Wed, Aug 19, 2020 at 09:28:41PM -0500, Scott Cheloha wrote:
> Hi,
> 
> I was auditing the tree for odd-looking time structure usage and I
> came across the UUID code in ldapd(8), uuid.c.
> 
> time_cmp() is backwards.  Or the caller is misusing it.  One or the
> other.  It returns -1 if tv1 exceeds tv2 but the comments in the
> caller indicate the opposite impression.  I don't think this code has
> ever worked as intended.
> 
> It would be a lot easier if we just threw the code out and used random
> UUIDs.  After reading over the RFC it seems to me that time-based
> UUIDs are collision-prone.  Their implementation is also complicated.
> Purely random UUIDs should effectively never collide and are trivial
> to implement.

RFC 4530, defining the entryUUID attribute, says this:

   UUID are to be generated in accordance with Section 4 of [RFC4122].
   In particular, servers MUST ensure that each generated UUID is unique
   in space and time.

Which doesn't rule out random uuids at all.  Is arc4random_buf() a better
attempt at ensuring the uuids are unique than doing complicated stuff with
clocks and mac addresses?  Maybe.  Windows has been generating random uuids
for about 20 years now.

> 
> However, assuming we can't just use random UUIDs, here's an attempt at
> improving this code:
> 
> - Use clock_gettime(2).  With nanosecond resolution we don't need
>   a 'counter'.
> 
> - Reduce the scope of all the static state to uuid_create().
> 
> - Shrink the loop.  Just read the clock until it changes, then decide
>   what to do re. seq_num.  This is effectively what the example code in
>   RFC 4122 does.
> 
> I'm unsure what the right thing to do is if the system clock predates
> the UUID epoch (Oct 15 1582).  My code just returns zero.  Maybe we
> should just kill the daemon in that case?  The UUIDv1 scheme breaks
> down if time is that seriously screwed up.
> 
> Is there an active ldapd(8) person?  Or at least someone with an
> ldapd(8) setup who can test this?

I'm kind of an active ldapd person, though I don't actually use it
actively.  I can try this out if need be.

> 
> Thoughts?
> 
> Index: uuid.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/ldapd/uuid.c,v
> retrieving revision 1.6
> diff -u -p -r1.6 uuid.c
> --- uuid.c    26 Apr 2018 12:42:51 -0000      1.6
> +++ uuid.c    20 Aug 2020 01:44:00 -0000
> @@ -63,27 +63,8 @@
>  
>  #include "uuid.h"
>  
> -static uint32_t seq_num;
> -static struct timeval last_time;
> -static int32_t counter;
> -static char nodeaddr[6];
> -
>  enum { UUID_NODE_MULTICAST = 0x80 };
>  
> -static int
> -time_cmp(struct timeval *tv1, struct timeval *tv2)
> -{
> -    if (tv1->tv_sec > tv2->tv_sec)
> -     return -1;
> -    if (tv1->tv_sec < tv2->tv_sec)
> -     return 1;
> -    if (tv1->tv_usec > tv2->tv_usec)
> -     return -1;
> -    if (tv1->tv_usec < tv2->tv_usec)
> -     return 1;
> -    return 0;
> -}
> -
>  static void
>  get_node_addr(char *addr)
>  {
> @@ -138,6 +119,40 @@ get_node_addr(char *addr)
>  }
>  
>  /*
> + * A UUID v1 timestamp:
> + *
> + * - 60 bits.
> + * - Unsigned.
> + * - Epoch at Oct 15 1582 00:00:00 UTC.
> + * - Increments every 100 nanoseconds.
> + */
> +#define UUID_EPOCH_OFFSET    12219292800LL
> +#define UUID_TIME_MAX                (1ULL << 60)
> +#define UUID_HZ                      10000000LL
> +#define NSEC_PER_UUID_TICK   100LL
> +
> +static uint64_t
> +get_uuid_timestamp(void)
> +{
> +     static const struct timespec min = { -UUID_EPOCH_OFFSET, 0 };
> +     static const struct timespec max = {
> +             UUID_TIME_MAX / UUID_HZ,
> +             UUID_TIME_MAX % UUID_HZ * NSEC_PER_UUID_TICK
> +     };
> +     struct timespec utc;
> +     uint64_t timestamp;
> +
> +     clock_gettime(CLOCK_REALTIME, &utc);
> +     if (timespeccmp(&utc, &min, <))
> +             return 0;
> +     if (timespeccmp(&max, &utc, <))
> +             return UUID_TIME_MAX;
> +     timestamp = (UUID_EPOCH_OFFSET + utc.tv_sec) * UUID_HZ;
> +     timestamp += utc.tv_nsec / NSEC_PER_UUID_TICK;
> +     return timestamp;
> +}
> +
> +/*
>   *    Creates a new UUID.
>   */
>  
> @@ -145,55 +160,32 @@ void
>  uuid_create(afsUUID *uuid)
>  {
>      static int uuid_inited = 0;
> -    struct timeval tv;
> -    int ret, got_time;
> +    static uint64_t last_time;
> +    static uint32_t seq_num;
> +    static char nodeaddr[6];
>      uint64_t dce_time;
>  
>      if (uuid_inited == 0) {
> -     gettimeofday(&last_time, NULL);
> +     last_time = get_uuid_timestamp();
>       seq_num = arc4random();
>       get_node_addr(nodeaddr);
>       uuid_inited = 1;
>      }
>  
> -    gettimeofday(&tv, NULL);
> -
> -    got_time = 0;
> +    while ((dce_time = get_uuid_timestamp()) == last_time)
> +     continue;
>  
> -    do {
> -     ret = time_cmp(&tv, &last_time);
> -     if (ret < 0) {
> -         /* Time went backward, just inc seq_num and be done.
> -          * seq_num is 6 + 8 bit field it the uuid, so let it wrap
> -          * around. don't let it be zero.
> -          */
> -         seq_num = (seq_num + 1) & 0x3fff ;
> -         if (seq_num == 0)
> -             seq_num++;
> -         got_time = 1;
> -         counter = 0;
> -         last_time = tv;
> -     } else if (ret > 0) {
> -         /* time went forward, reset counter and be happy */
> -         last_time = tv;
> -         counter = 0;
> -         got_time = 1;
> -     } else {
> -#define UUID_MAX_HZ (1) /* make this bigger fix you have larger tickrate */
> -#define MULTIPLIER_100_NANO_SEC 10
> -         if (++counter < UUID_MAX_HZ * MULTIPLIER_100_NANO_SEC)
> -             got_time = 1;
> -     }
> -    } while(!got_time);
> +    if (dce_time < last_time) {
> +     /* Time went backward, just inc seq_num and be done.
> +      * seq_num is 6 + 8 bit field it the uuid, so let it wrap
> +      * around. don't let it be zero.
> +      */
> +     seq_num = (seq_num + 1) & 0x3fff ;
> +     if (seq_num == 0)
> +         seq_num++;
> +    }
>  
> -    /*
> -     * now shift time to dce_time, epoch 00:00:00:00, 15 October 1582
> -     * dce time ends year ~3400, so start to worry now
> -     */
> -
> -    dce_time = tv.tv_usec * MULTIPLIER_100_NANO_SEC + counter;
> -    dce_time += ((uint64_t)tv.tv_sec) * 10000000;
> -    dce_time += (((uint64_t)0x01b21dd2) << 32) + 0x13814000;
> +    last_time = dce_time;
>  
>      uuid->time_low = dce_time & 0xffffffff;
>      uuid->time_mid = 0xffff & (dce_time >> 32);
> 

Reply via email to