xref: /reactos/ntoskrnl/ex/uuid.c (revision 7b1049c8)
1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS kernel
4  * FILE:            ntoskrnl/ex/uuid.c
5  * PURPOSE:         UUID generator
6  * PROGRAMMERS:     Eric Kohl
7  *                  Thomas Weidenmueller
8  *                  Pierre Schweitzer
9  */
10 
11 /* INCLUDES *****************************************************************/
12 
13 #include <ntoskrnl.h>
14 #define NDEBUG
15 #include <debug.h>
16 
17 #define SEED_BUFFER_SIZE 6
18 
19 /* Number of 100ns ticks per clock tick. To be safe, assume that the clock
20    resolution is at least 1000 * 100 * (1/1000000) = 1/10 of a second */
21 #define TICKS_PER_CLOCK_TICK 1000
22 #define SECSPERDAY  86400
23 #define TICKSPERSEC 10000000
24 
25 /* UUID system time starts at October 15, 1582 */
26 #define SECS_15_OCT_1582_TO_1601  ((17 + 30 + 31 + 365 * 18 + 5) * SECSPERDAY)
27 #define TICKS_15_OCT_1582_TO_1601 ((ULONGLONG)SECS_15_OCT_1582_TO_1601 * TICKSPERSEC)
28 
29 /* 10000 in 100-ns model = 0.1 microsecond */
30 #define TIME_FRAME 10000
31 
32 /* GLOBALS ****************************************************************/
33 
34 FAST_MUTEX ExpUuidLock;
35 LARGE_INTEGER ExpUuidLastTimeAllocated;
36 ULONG ExpUuidSequenceNumber = 0;
37 BOOLEAN ExpUuidSequenceNumberValid;
38 BOOLEAN ExpUuidSequenceNumberNotSaved = FALSE;
39 UUID_CACHED_VALUES_STRUCT ExpUuidCachedValues = {0ULL, 0xFFFFFFFF, {{0, 0, {0x80, 0x6E, 0x6F, 0x6E, 0x69, 0x63}}}};
40 BOOLEAN ExpUuidCacheValid = FALSE;
41 ULONG ExpLuidIncrement = 1;
42 LARGE_INTEGER ExpLuid = {{0x3e9, 0x0}};
43 
44 /* FUNCTIONS ****************************************************************/
45 
46 /*
47  * @implemented
48  */
49 CODE_SEG("INIT")
50 BOOLEAN
51 NTAPI
52 ExpUuidInitialization(VOID)
53 {
54     ExInitializeFastMutex(&ExpUuidLock);
55 
56     ExpUuidSequenceNumberValid = FALSE;
57     KeQuerySystemTime(&ExpUuidLastTimeAllocated);
58 
59     return TRUE;
60 }
61 
62 
63 /*
64  * @implemented
65  */
66 #define VALUE_BUFFER_SIZE 20
67 static NTSTATUS
68 ExpUuidLoadSequenceNumber(PULONG Sequence)
69 {
70     UCHAR ValueBuffer[VALUE_BUFFER_SIZE];
71     PKEY_VALUE_PARTIAL_INFORMATION ValueInfo;
72     OBJECT_ATTRIBUTES ObjectAttributes;
73     UNICODE_STRING KeyName, ValueName;
74     HANDLE KeyHandle;
75     ULONG ValueLength;
76     NTSTATUS Status;
77 
78     PAGED_CODE();
79 
80     RtlInitUnicodeString(&KeyName, L"\\Registry\\Machine\\Software\\Microsoft\\Rpc");
81     RtlInitUnicodeString(&ValueName, L"UuidSequenceNumber");
82 
83     InitializeObjectAttributes(&ObjectAttributes,
84                                &KeyName,
85                                OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
86                                NULL,
87                                NULL);
88     Status = ZwOpenKey(&KeyHandle, GENERIC_READ, &ObjectAttributes);
89     if (!NT_SUCCESS(Status))
90     {
91         DPRINT("ZwOpenKey() failed (Status %lx)\n", Status);
92         return Status;
93     }
94 
95     ValueInfo = (PKEY_VALUE_PARTIAL_INFORMATION)ValueBuffer;
96     Status = ZwQueryValueKey(KeyHandle,
97                              &ValueName,
98                              KeyValuePartialInformation,
99                              ValueBuffer,
100                              VALUE_BUFFER_SIZE,
101                              &ValueLength);
102     ZwClose(KeyHandle);
103     if (!NT_SUCCESS(Status))
104     {
105         DPRINT("ZwQueryValueKey() failed (Status %lx)\n", Status);
106         return Status;
107     }
108 
109     if (ValueInfo->Type != REG_DWORD || ValueInfo->DataLength != sizeof(DWORD))
110     {
111         return STATUS_UNSUCCESSFUL;
112     }
113 
114     *Sequence = *((PULONG)ValueInfo->Data);
115 
116     DPRINT("Loaded sequence %lx\n", *Sequence);
117 
118     return STATUS_SUCCESS;
119 }
120 #undef VALUE_BUFFER_SIZE
121 
122 /*
123  * @implemented
124  */
125 static NTSTATUS
126 ExpUuidSaveSequenceNumber(PULONG Sequence)
127 {
128     OBJECT_ATTRIBUTES ObjectAttributes;
129     UNICODE_STRING KeyName, ValueName;
130     HANDLE KeyHandle;
131     NTSTATUS Status;
132 
133     PAGED_CODE();
134 
135     RtlInitUnicodeString(&KeyName, L"\\Registry\\Machine\\Software\\Microsoft\\Rpc");
136     RtlInitUnicodeString(&ValueName, L"UuidSequenceNumber");
137 
138     InitializeObjectAttributes(&ObjectAttributes,
139                                &KeyName,
140                                OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
141                                NULL,
142                                NULL);
143     Status = ZwOpenKey(&KeyHandle,
144                        GENERIC_READ | GENERIC_WRITE,
145                        &ObjectAttributes);
146     if (!NT_SUCCESS(Status))
147     {
148         DPRINT("ZwOpenKey() failed (Status %lx)\n", Status);
149         return Status;
150     }
151 
152     Status = ZwSetValueKey(KeyHandle,
153                            &ValueName,
154                            0,
155                            REG_DWORD,
156                            Sequence,
157                            sizeof(ULONG));
158     ZwClose(KeyHandle);
159     if (!NT_SUCCESS(Status))
160     {
161         DPRINT("ZwSetValueKey() failed (Status %lx)\n", Status);
162     }
163 
164     return Status;
165 }
166 
167 /*
168  * @implemented
169  * Warning! This function must be called
170  * with ExpUuidLock held!
171  */
172 static VOID
173 ExpUuidSaveSequenceNumberIf(VOID)
174 {
175     NTSTATUS Status;
176 
177     PAGED_CODE();
178 
179     /* Only save sequence if it has to */
180     if (ExpUuidSequenceNumberNotSaved == TRUE)
181     {
182         Status = ExpUuidSaveSequenceNumber(&ExpUuidSequenceNumber);
183         if (NT_SUCCESS(Status))
184         {
185             ExpUuidSequenceNumberNotSaved = FALSE;
186         }
187     }
188 }
189 
190 /*
191  * @implemented
192  * Warning! This function must be called
193  * with ExpUuidLock held!
194  */
195 static NTSTATUS
196 ExpAllocateUuids(PULARGE_INTEGER Time,
197                  PULONG Range,
198                  PULONG Sequence)
199 {
200     NTSTATUS Status;
201     LARGE_INTEGER Counter, Frequency, CurrentTime, TimeDiff, ClockDiff;
202 
203     PAGED_CODE();
204 
205     /* Initialize sequence number */
206     if (!ExpUuidSequenceNumberValid)
207     {
208         /* Try to load sequence number */
209         Status = ExpUuidLoadSequenceNumber(&ExpUuidSequenceNumber);
210         if (NT_SUCCESS(Status))
211         {
212             ++ExpUuidSequenceNumber;
213         }
214         else
215         {
216             /* If we cannot, generate a "true" random */
217             Counter = KeQueryPerformanceCounter(&Frequency);
218             ExpUuidSequenceNumber ^= (ULONG_PTR)&Status ^ (ULONG_PTR)Sequence ^ Counter.LowPart ^ Counter.HighPart;
219         }
220 
221         /* It's valid and to be saved */
222         ExpUuidSequenceNumberValid = TRUE;
223         ExpUuidSequenceNumberNotSaved = TRUE;
224     }
225 
226     KeQuerySystemTime(&CurrentTime);
227     TimeDiff.QuadPart = CurrentTime.QuadPart - ExpUuidLastTimeAllocated.QuadPart;
228     /* If time went backwards, change sequence (see RFC example) */
229     if (TimeDiff.QuadPart < 0)
230     {
231         ++ExpUuidSequenceNumber;
232         TimeDiff.QuadPart = 2 * TIME_FRAME;
233 
234         /* It's to be saved */
235         ExpUuidSequenceNumberNotSaved = TRUE;
236         ExpUuidLastTimeAllocated.QuadPart = CurrentTime.QuadPart - 2 * TIME_FRAME;
237     }
238 
239     if (TimeDiff.QuadPart == 0)
240     {
241         return STATUS_RETRY;
242     }
243 
244     /* If time diff > 0.1ms, squash it to reduce it to keep our clock resolution */
245     if (TimeDiff.HighPart > 0 || TimeDiff.QuadPart > TICKS_PER_CLOCK_TICK * TIME_FRAME)
246     {
247         TimeDiff.QuadPart = TICKS_PER_CLOCK_TICK * TIME_FRAME;
248     }
249 
250     if (TimeDiff.HighPart < 0 || TimeDiff.QuadPart <= TIME_FRAME)
251     {
252         *Range = TimeDiff.QuadPart;
253         ClockDiff.QuadPart = 0LL;
254     }
255     else
256     {
257         *Range = TIME_FRAME;
258         ClockDiff.QuadPart = TimeDiff.QuadPart - TIME_FRAME;
259         --ClockDiff.HighPart;
260     }
261 
262     Time->QuadPart = CurrentTime.QuadPart - *Range - ClockDiff.QuadPart;
263     ExpUuidLastTimeAllocated.QuadPart = CurrentTime.QuadPart - ClockDiff.QuadPart;
264     *Sequence = ExpUuidSequenceNumber;
265 
266     return STATUS_SUCCESS;
267 }
268 
269 /*
270  * @implemented
271  * Warning! This function must be called
272  * with ExpUuidLock held!
273  */
274 static NTSTATUS
275 ExpUuidGetValues(PUUID_CACHED_VALUES_STRUCT CachedValues)
276 {
277     NTSTATUS Status;
278     ULARGE_INTEGER Time;
279     ULONG Range;
280     ULONG Sequence;
281 
282     PAGED_CODE();
283 
284     /* Allocate UUIDs */
285     Status = ExpAllocateUuids(&Time, &Range, &Sequence);
286     if (Status == STATUS_RETRY)
287     {
288         return Status;
289     }
290 
291     if (!NT_SUCCESS(Status))
292     {
293         return STATUS_NO_MEMORY;
294     }
295 
296     /* We need at least one UUID */
297     ASSERT(Range != 0);
298 
299     /* Set up our internal cache
300      * See format_uuid_v1 in RFC4122 for magic values
301      */
302     CachedValues->ClockSeqLow = Sequence;
303     CachedValues->ClockSeqHiAndReserved = (Sequence & 0x3F00) >> 8;
304     CachedValues->ClockSeqHiAndReserved |= 0x80;
305     CachedValues->AllocatedCount = Range;
306 
307     /*
308      * Time is relative to UUID time
309      * And we set last time range for all the possibly
310      * returnable UUID
311      */
312     Time.QuadPart += TICKS_15_OCT_1582_TO_1601;
313     CachedValues->Time = Time.QuadPart + (Range - 1);
314 
315     return STATUS_SUCCESS;
316 }
317 
318 /*
319  * @implemented
320  */
321 CODE_SEG("INIT")
322 BOOLEAN
323 NTAPI
324 ExLuidInitialization(VOID)
325 {
326     return TRUE;
327 }
328 
329 /*
330  * @implemented
331  */
332 VOID
333 NTAPI
334 ExAllocateLocallyUniqueId(OUT LUID *LocallyUniqueId)
335 {
336     /* Atomically increment the luid */
337     *(LONG64*)LocallyUniqueId = InterlockedExchangeAdd64(&ExpLuid.QuadPart,
338                                                          ExpLuidIncrement);
339 }
340 
341 
342 /*
343  * @implemented
344  */
345 NTSTATUS
346 NTAPI
347 NtAllocateLocallyUniqueId(OUT LUID *LocallyUniqueId)
348 {
349     KPROCESSOR_MODE PreviousMode;
350     PAGED_CODE();
351 
352     /* Probe if user mode */
353     PreviousMode = ExGetPreviousMode();
354     if (PreviousMode != KernelMode)
355     {
356         _SEH2_TRY
357         {
358             ProbeForWrite(LocallyUniqueId,
359                           sizeof(LUID),
360                           sizeof(ULONG));
361         }
362         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
363         {
364             _SEH2_YIELD(return _SEH2_GetExceptionCode());
365         }
366         _SEH2_END;
367     }
368 
369     /* Do the allocation */
370     ExAllocateLocallyUniqueId(LocallyUniqueId);
371     return STATUS_SUCCESS;
372 }
373 
374 /*
375  * @implemented
376  */
377 NTSTATUS
378 NTAPI
379 ExUuidCreate(OUT UUID *Uuid)
380 {
381     NTSTATUS Status;
382     LONG AllocatedCount;
383     LARGE_INTEGER Time;
384     BOOLEAN Valid;
385 
386     PAGED_CODE();
387 
388     Status = STATUS_SUCCESS;
389     /* Loop until we have an UUID to return */
390     while (TRUE)
391     {
392         /* Try to gather node values */
393         do
394         {
395             Time.QuadPart = ExpUuidCachedValues.Time;
396 
397             RtlCopyMemory(Uuid->Data4,
398                           ExpUuidCachedValues.GuidInit,
399                           sizeof(Uuid->Data4));
400 
401             Valid = ExpUuidCacheValid;
402             AllocatedCount = InterlockedDecrement(&ExpUuidCachedValues.AllocatedCount);
403         }
404         /* Loop till we can do it without being disturbed */
405         while (Time.QuadPart != ExpUuidCachedValues.Time);
406 
407         /* We have more than an allocated UUID left, that's OK to return! */
408         if (AllocatedCount >= 0)
409         {
410             break;
411         }
412 
413         /*
414          * Here, we're out of UUIDs, we need to allocate more
415          * We need to be alone to do it, so lock the mutex
416          */
417         ExAcquireFastMutex(&ExpUuidLock);
418         if (Time.QuadPart == ExpUuidCachedValues.Time)
419         {
420             /* If allocation fails, bail out! */
421             Status = ExpUuidGetValues(&ExpUuidCachedValues);
422             if (Status != STATUS_SUCCESS)
423             {
424                 ExReleaseFastMutex(&ExpUuidLock);
425                 return Status;
426             }
427 
428             /* Save our current sequence if changed */
429             ExpUuidSaveSequenceNumberIf();
430         }
431         ExReleaseFastMutex(&ExpUuidLock);
432     }
433 
434     /*
435      * Once here, we've got an UUID to return
436      * But, if our init wasn't sane, then, make
437      * sure it's only used locally
438      */
439     if (!Valid)
440     {
441         Status = RPC_NT_UUID_LOCAL_ONLY;
442     }
443 
444     /* Set our timestamp - see RFC4211 */
445     Time.QuadPart -= AllocatedCount;
446     Uuid->Data1 = Time.LowPart;
447     Uuid->Data2 = Time.HighPart;
448     /* We also set the bit for GUIDv1 */
449     Uuid->Data3 = ((Time.HighPart >> 16) & 0x0FFF) | 0x1000;
450 
451     return Status;
452 }
453 
454 /*
455  * @implemented
456  */
457 NTSTATUS
458 NTAPI
459 NtAllocateUuids(OUT PULARGE_INTEGER Time,
460                 OUT PULONG Range,
461                 OUT PULONG Sequence,
462                 OUT PUCHAR Seed)
463 {
464     ULARGE_INTEGER IntTime;
465     ULONG IntRange, IntSequence;
466     NTSTATUS Status;
467     KPROCESSOR_MODE PreviousMode;
468 
469     PAGED_CODE();
470 
471     /* Probe if user mode */
472     PreviousMode = ExGetPreviousMode();
473     if (PreviousMode != KernelMode)
474     {
475         _SEH2_TRY
476         {
477             ProbeForWrite(Time,
478                           sizeof(ULARGE_INTEGER),
479                           sizeof(ULONG));
480 
481             ProbeForWrite(Range,
482                           sizeof(ULONG),
483                           sizeof(ULONG));
484 
485             ProbeForWrite(Sequence,
486                           sizeof(ULONG),
487                           sizeof(ULONG));
488 
489             ProbeForWrite(Seed,
490                           SEED_BUFFER_SIZE,
491                           sizeof(UCHAR));
492         }
493         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
494         {
495             _SEH2_YIELD(return _SEH2_GetExceptionCode());
496         }
497         _SEH2_END;
498     }
499 
500     /* During allocation we must be alone */
501     ExAcquireFastMutex(&ExpUuidLock);
502 
503     Status = ExpAllocateUuids(&IntTime,
504                               &IntRange,
505                               &IntSequence);
506     if (!NT_SUCCESS(Status))
507     {
508         ExReleaseFastMutex(&ExpUuidLock);
509         return Status;
510     }
511 
512     /* If sequence number was changed, save it */
513     ExpUuidSaveSequenceNumberIf();
514 
515     /* Allocation done, so we can release */
516     ExReleaseFastMutex(&ExpUuidLock);
517 
518     /* Write back UUIDs to caller */
519     _SEH2_TRY
520     {
521         Time->QuadPart = IntTime.QuadPart;
522         *Range = IntRange;
523         *Sequence = IntSequence;
524 
525         RtlCopyMemory(Seed,
526                       &ExpUuidCachedValues.NodeId[0],
527                       SEED_BUFFER_SIZE);
528 
529         Status = STATUS_SUCCESS;
530     }
531     _SEH2_EXCEPT(ExSystemExceptionFilter())
532     {
533         Status = _SEH2_GetExceptionCode();
534     }
535     _SEH2_END;
536 
537     return Status;
538 }
539 
540 
541 /*
542  * @implemented
543  */
544 NTSTATUS
545 NTAPI
546 NtSetUuidSeed(IN PUCHAR Seed)
547 {
548     NTSTATUS Status;
549     BOOLEAN GotContext;
550     PACCESS_TOKEN Token;
551     SECURITY_SUBJECT_CONTEXT SubjectContext;
552     LUID CallerLuid, SystemLuid = SYSTEM_LUID;
553 
554     PAGED_CODE();
555 
556     /* Should only be done by umode */
557     ASSERT(KeGetPreviousMode() != KernelMode);
558 
559     /* No context to release */
560     GotContext = FALSE;
561     _SEH2_TRY
562     {
563         /* Get our caller context and remember to release it */
564         SeCaptureSubjectContext(&SubjectContext);
565         GotContext = TRUE;
566 
567         /* Get caller access token and its associated ID */
568         Token = SeQuerySubjectContextToken(&SubjectContext);
569         Status = SeQueryAuthenticationIdToken(Token, &CallerLuid);
570         if (!NT_SUCCESS(Status))
571         {
572             RtlRaiseStatus(Status);
573         }
574 
575         /* This call is only allowed for SYSTEM */
576         if (!RtlEqualLuid(&CallerLuid, &SystemLuid))
577         {
578             RtlRaiseStatus(STATUS_ACCESS_DENIED);
579         }
580 
581         /* Check for buffer validity and then copy it to our seed */
582         ProbeForRead(Seed, SEED_BUFFER_SIZE, sizeof(UCHAR));
583         RtlCopyMemory(&ExpUuidCachedValues.NodeId[0], Seed, SEED_BUFFER_SIZE);
584 
585         /*
586          * According to RFC 4122, UUID seed is based on MAC addresses
587          * If it is randomly set, then, it must have its multicast be set
588          * to be valid and avoid collisions
589          * Reflect it here
590          */
591         ExpUuidCacheValid = ~(*Seed >> 7) & 1;
592 
593         Status = STATUS_SUCCESS;
594     }
595     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
596     {
597         Status = _SEH2_GetExceptionCode();
598     }
599     _SEH2_END;
600 
601     /* Release context if required */
602     if (GotContext)
603     {
604         SeReleaseSubjectContext(&SubjectContext);
605     }
606 
607     return Status;
608 }
609 
610 /* EOF */
611