Remix.run Logo
NPM UUID's random number gen contains shared mutable state bug since 3 weeks
2 points by jbverschoor 7 hours ago | 2 comments

(copied from the other thread)

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.

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 else's "random number" and know about it. The fun things you can do with those numbers!

jbverschoor 7 hours ago | parent | next [-]

(previously https://news.ycombinator.com/item?id=48060054 )

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

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
jbverschoor 7 hours ago | parent | prev [-]

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.