Comment by jbverschoor

21 hours ago

Changed 3 weeks ago:

uuid/src/rng.ts : the random array is const. Every call will share the same random number. Subsequent call will update your old random code, so if you generated something important... good luck

The old code used to do a slice() which creates a new copy.

Might be unintentional. Although I have no idea how this would pass any tests, as you would think to test generating 2 randomnumbers and hope they are not the same.

Didn't actually want to write a test myself.. but I miss Claudia confirmed it. Pretty concearning.

Synchronous / serial calls:

   import rng from './rng';
   
   const a = rng();
   console.log('a after first call: ', Array.from(a));
   
   const b = rng();
   console.log('a after second call:', Array.from(a));
   console.log('b after second call:', Array.from(b));
   
   console.log('a === b (same reference)?    ', a === b);
   console.log('a equals b (same contents)?  ', a.every((v, i) => v === b[i]));

output:

   a after first call:  [
     101, 193, 125,  19, 142,
     136, 181, 140, 209, 224,
     176, 153, 179, 248, 246,
     166
   ]
   a after second call: [
       4,  29, 48, 215, 162,  60,
      64,  23, 78, 137,   2, 186,
     230, 249, 70, 224
   ]
   b after second call: [
       4,  29, 48, 215, 162,  60,
      64,  23, 78, 137,   2, 186,
     230, 249, 70, 224
   ]
   a === b (same reference)?     true
   a equals b (same contents)?   true
   

and aynchronous calls:

   import rng from './rng';
   
   async function getId() {
      const bytes = rng();
      await new Promise(r => setTimeout(r, 0)); // yield to the event loop
      return Array.from(bytes);
   }
   
   const [id1, id2] = await Promise.all([getId(), getId()]);
   console.log('id1:', id1);
   console.log('id2:', id2);
   console.log('identical?', id1.every((v, i) => v === id2[i]));

output:

   id1 captured:  [
      61, 116, 151,  35, 153,
      75, 105,  15,  59, 235,
     162, 215, 224, 115,  31,
     122
   ]
   id2 captured:  [
      13,  3,  84,  28, 22, 176,
     160, 70,  67, 246,  1,  37,
      38, 61, 171,  23
   ]
   id1 after await: [
      13,  3,  84,  28, 22, 176,
     160, 70,  67, 246,  1,  37,
      38, 61, 171,  23
   ]
   id2 after await: [
      13,  3,  84,  28, 22, 176,
     160, 70,  67, 246,  1,  37,
      38, 61, 171,  23
   ]
   ---
   final id1: [
      13,  3,  84,  28, 22, 176,
     160, 70,  67, 246,  1,  37,
      38, 61, 171,  23
   ]
   final id2: [
      13,  3,  84,  28, 22, 176,
     160, 70,  67, 246,  1,  37,
      38, 61, 171,  23
   ]
   identical? true

  • Shouldn't your test follow the pattern of how rng() is actually being used in the uuid.ts code internally?

    Your test is more-or-less contrived to fail given the tradeoff to avoid repeated memory allocations but that doesn't say much about the actual usage in uuid generation since it's not exported for general purpose use.

    Presumably they had some hot path somewhere where rng() is called in a loop and this optimization made sense with awareness that it could be misused as in your example breaking the contract ensuring randomness, which (hopefully) they're not actually doing anywhere.

    Unless I'm missing something replacing the package over this with a less vetted implementation seems excessive and possibly even counterproductive.

    • I don't believe so. Sure it's not an issue after some checks, but it's very easy to shoot yourself in the foot like that. I get the micro-optimization for the allocation.. But it's not clear / documented. At the minimum, the function should be renamed to reflect the inner workings.

      The function is a module, and it doesn't do what you'd expect.

https://github.com/uuidjs/uuid/blob/e1f42a354593093ba0479f0b...

became

https://github.com/uuidjs/uuid/blob/f2c235f93059325fa43e1106...

Welp.. time to patch and update everything again. Another day, another npm-package headache. Very odd()

Attack vector: call the rng(), and send the result somewhere. You now have now overwritten someone elses "random number" and know about it. The fun things you can do with those numbers!

  • Seems to be "safe" because of it's not exported, and the results get used in a different way. Still is a bug in my book.