xref: /reactos/ntoskrnl/ex/uuid.c (revision 8a978a17)
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             C_ASSERT(sizeof(ExpUuidCachedValues.GuidInit) == sizeof(Uuid->Data4));
398             RtlCopyMemory(Uuid->Data4,
399                           ExpUuidCachedValues.GuidInit,
400                           sizeof(Uuid->Data4));
401 
402             Valid = ExpUuidCacheValid;
403             AllocatedCount = InterlockedDecrement(&ExpUuidCachedValues.AllocatedCount);
404         }
405         /* Loop till we can do it without being disturbed */
406         while (Time.QuadPart != ExpUuidCachedValues.Time);
407 
408         /* We have more than an allocated UUID left, that's OK to return! */
409         if (AllocatedCount >= 0)
410         {
411             break;
412         }
413 
414         /*
415          * Here, we're out of UUIDs, we need to allocate more
416          * We need to be alone to do it, so lock the mutex
417          */
418         ExAcquireFastMutex(&ExpUuidLock);
419         if (Time.QuadPart == ExpUuidCachedValues.Time)
420         {
421             /* If allocation fails, bail out! */
422             Status = ExpUuidGetValues(&ExpUuidCachedValues);
423             if (Status != STATUS_SUCCESS)
424             {
425                 ExReleaseFastMutex(&ExpUuidLock);
426                 return Status;
427             }
428 
429             /* Save our current sequence if changed */
430             ExpUuidSaveSequenceNumberIf();
431         }
432         ExReleaseFastMutex(&ExpUuidLock);
433     }
434 
435     /*
436      * Once here, we've got an UUID to return
437      * But, if our init wasn't sane, then, make
438      * sure it's only used locally
439      */
440     if (!Valid)
441     {
442         Status = RPC_NT_UUID_LOCAL_ONLY;
443     }
444 
445     /* Set our timestamp - see RFC4211 */
446     Time.QuadPart -= AllocatedCount;
447     Uuid->Data1 = Time.LowPart;
448     Uuid->Data2 = Time.HighPart;
449     /* We also set the bit for GUIDv1 */
450     Uuid->Data3 = ((Time.HighPart >> 16) & 0x0FFF) | 0x1000;
451 
452     return Status;
453 }
454 
455 /*
456  * @implemented
457  */
458 NTSTATUS
459 NTAPI
460 NtAllocateUuids(OUT PULARGE_INTEGER Time,
461                 OUT PULONG Range,
462                 OUT PULONG Sequence,
463                 OUT PUCHAR Seed)
464 {
465     ULARGE_INTEGER IntTime;
466     ULONG IntRange, IntSequence;
467     NTSTATUS Status;
468     KPROCESSOR_MODE PreviousMode;
469 
470     PAGED_CODE();
471 
472     /* Probe if user mode */
473     PreviousMode = ExGetPreviousMode();
474     if (PreviousMode != KernelMode)
475     {
476         _SEH2_TRY
477         {
478             ProbeForWrite(Time,
479                           sizeof(ULARGE_INTEGER),
480                           sizeof(ULONG));
481 
482             ProbeForWrite(Range,
483                           sizeof(ULONG),
484                           sizeof(ULONG));
485 
486             ProbeForWrite(Sequence,
487                           sizeof(ULONG),
488                           sizeof(ULONG));
489 
490             ProbeForWrite(Seed,
491                           SEED_BUFFER_SIZE,
492                           sizeof(UCHAR));
493         }
494         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
495         {
496             _SEH2_YIELD(return _SEH2_GetExceptionCode());
497         }
498         _SEH2_END;
499     }
500 
501     /* During allocation we must be alone */
502     ExAcquireFastMutex(&ExpUuidLock);
503 
504     Status = ExpAllocateUuids(&IntTime,
505                               &IntRange,
506                               &IntSequence);
507     if (!NT_SUCCESS(Status))
508     {
509         ExReleaseFastMutex(&ExpUuidLock);
510         return Status;
511     }
512 
513     /* If sequence number was changed, save it */
514     ExpUuidSaveSequenceNumberIf();
515 
516     /* Allocation done, so we can release */
517     ExReleaseFastMutex(&ExpUuidLock);
518 
519     /* Write back UUIDs to caller */
520     _SEH2_TRY
521     {
522         Time->QuadPart = IntTime.QuadPart;
523         *Range = IntRange;
524         *Sequence = IntSequence;
525 
526         RtlCopyMemory(Seed,
527                       &ExpUuidCachedValues.NodeId[0],
528                       SEED_BUFFER_SIZE);
529 
530         Status = STATUS_SUCCESS;
531     }
532     _SEH2_EXCEPT(ExSystemExceptionFilter())
533     {
534         Status = _SEH2_GetExceptionCode();
535     }
536     _SEH2_END;
537 
538     return Status;
539 }
540 
541 
542 /*
543  * @implemented
544  */
545 NTSTATUS
546 NTAPI
547 NtSetUuidSeed(IN PUCHAR Seed)
548 {
549     NTSTATUS Status;
550     BOOLEAN GotContext;
551     PACCESS_TOKEN Token;
552     SECURITY_SUBJECT_CONTEXT SubjectContext;
553     LUID CallerLuid, SystemLuid = SYSTEM_LUID;
554 
555     PAGED_CODE();
556 
557     /* Should only be done by umode */
558     ASSERT(KeGetPreviousMode() != KernelMode);
559 
560     /* No context to release */
561     GotContext = FALSE;
562     _SEH2_TRY
563     {
564         /* Get our caller context and remember to release it */
565         SeCaptureSubjectContext(&SubjectContext);
566         GotContext = TRUE;
567 
568         /* Get caller access token and its associated ID */
569         Token = SeQuerySubjectContextToken(&SubjectContext);
570         Status = SeQueryAuthenticationIdToken(Token, &CallerLuid);
571         if (!NT_SUCCESS(Status))
572         {
573             RtlRaiseStatus(Status);
574         }
575 
576         /* This call is only allowed for SYSTEM */
577         if (!RtlEqualLuid(&CallerLuid, &SystemLuid))
578         {
579             RtlRaiseStatus(STATUS_ACCESS_DENIED);
580         }
581 
582         /* Check for buffer validity and then copy it to our seed */
583         ProbeForRead(Seed, SEED_BUFFER_SIZE, sizeof(UCHAR));
584         RtlCopyMemory(&ExpUuidCachedValues.NodeId[0], Seed, SEED_BUFFER_SIZE);
585 
586         /*
587          * According to RFC 4122, UUID seed is based on MAC addresses
588          * If it is randomly set, then, it must have its multicast be set
589          * to be valid and avoid collisions
590          * Reflect it here
591          */
592         ExpUuidCacheValid = ~(*Seed >> 7) & 1;
593 
594         Status = STATUS_SUCCESS;
595     }
596     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
597     {
598         Status = _SEH2_GetExceptionCode();
599     }
600     _SEH2_END;
601 
602     /* Release context if required */
603     if (GotContext)
604     {
605         SeReleaseSubjectContext(&SubjectContext);
606     }
607 
608     return Status;
609 }
610 
611 /* EOF */
612