xref: /reactos/ntoskrnl/ob/oblink.c (revision 9592728f)
1 /*
2 * PROJECT:         ReactOS Kernel
3 * LICENSE:         GPL - See COPYING in the top level directory
4 * FILE:            ntoskrnl/ob/oblink.c
5 * PURPOSE:         Implements symbolic links
6 * PROGRAMMERS:     Alex Ionescu (alex@relsoft.net)
7 *                  David Welch (welch@mcmail.com)
8 *                  Pierre Schweitzer
9 */
10 
11 /* INCLUDES *****************************************************************/
12 
13 #include <ntoskrnl.h>
14 #define NDEBUG
15 #include <debug.h>
16 
17 /* GLOBALS ******************************************************************/
18 
19 POBJECT_TYPE ObpSymbolicLinkObjectType = NULL;
20 
21 /* PRIVATE FUNCTIONS *********************************************************/
22 
23 VOID
24 ObpProcessDosDeviceSymbolicLink(IN POBJECT_SYMBOLIC_LINK SymbolicLink,
25                                 IN BOOLEAN DeleteLink)
26 {
27     PDEVICE_MAP DeviceMap;
28     UNICODE_STRING TargetPath, LocalTarget;
29     POBJECT_DIRECTORY NameDirectory, DirectoryObject;
30     ULONG MaxReparse;
31     OBP_LOOKUP_CONTEXT LookupContext;
32     ULONG DriveType;
33     POBJECT_HEADER ObjectHeader;
34     POBJECT_HEADER_NAME_INFO ObjectNameInfo;
35     BOOLEAN DirectoryLocked;
36     PVOID Object;
37 
38     /*
39      * To prevent endless reparsing, setting an upper limit on the
40      * number of reparses.
41      */
42     MaxReparse = 32;
43     NameDirectory = NULL;
44 
45     /* Get header data */
46     ObjectHeader = OBJECT_TO_OBJECT_HEADER(SymbolicLink);
47     ObjectNameInfo = OBJECT_HEADER_TO_NAME_INFO(ObjectHeader);
48 
49     /* Check if we have a directory in our symlink to use */
50     if (ObjectNameInfo != NULL)
51     {
52         NameDirectory = ObjectNameInfo->Directory;
53     }
54 
55     /* Initialize lookup context */
56     ObpInitializeLookupContext(&LookupContext);
57 
58     /*
59      * If we have to create the link, locate the IoDeviceObject if any
60      * this symbolic link points to.
61      */
62     if (SymbolicLink->LinkTargetObject != NULL || !DeleteLink)
63     {
64         /* Start the search from the root */
65         DirectoryObject = ObpRootDirectoryObject;
66 
67         /* Keep track of our progress while parsing the name */
68         LocalTarget = SymbolicLink->LinkTarget;
69 
70         /* If LUID mappings are enabled, use system map */
71         if (ObpLUIDDeviceMapsEnabled != 0)
72         {
73             DeviceMap = ObSystemDeviceMap;
74         }
75         /* Otherwise, use the one in the process */
76         else
77         {
78             DeviceMap = PsGetCurrentProcess()->DeviceMap;
79         }
80 
81 ReparseTargetPath:
82         /*
83          * If we have a device map active, check whether we have a drive
84          * letter prefixed with ??, if so, chomp it
85          */
86         if (DeviceMap != NULL)
87         {
88             if (!((ULONG_PTR)(LocalTarget.Buffer) & 7))
89             {
90                 if (DeviceMap->DosDevicesDirectory != NULL)
91                 {
92                     if (LocalTarget.Length >= ObpDosDevicesShortName.Length &&
93                         (*(PULONGLONG)LocalTarget.Buffer ==
94                          ObpDosDevicesShortNamePrefix.Alignment.QuadPart))
95                     {
96                         DirectoryObject = DeviceMap->DosDevicesDirectory;
97 
98                         LocalTarget.Length -= ObpDosDevicesShortName.Length;
99                         LocalTarget.Buffer += (ObpDosDevicesShortName.Length / sizeof(WCHAR));
100                     }
101                 }
102             }
103         }
104 
105         /* Try walking the target path and open each part of the path */
106         while (TRUE)
107         {
108             if (LocalTarget.Buffer[0] == OBJ_NAME_PATH_SEPARATOR)
109             {
110                 ++LocalTarget.Buffer;
111                 LocalTarget.Length -= sizeof(WCHAR);
112             }
113 
114             /* Remember the current component of the target path */
115             TargetPath = LocalTarget;
116 
117             /* Move forward to the next component of the target path */
118             if (LocalTarget.Length != 0)
119             {
120                 do
121                 {
122                     if (LocalTarget.Buffer[0] == OBJ_NAME_PATH_SEPARATOR)
123                     {
124                         break;
125                     }
126 
127                     ++LocalTarget.Buffer;
128                     LocalTarget.Length -= sizeof(WCHAR);
129                 }
130                 while (LocalTarget.Length != 0);
131             }
132 
133             TargetPath.Length -= LocalTarget.Length;
134 
135             /*
136              * Finished processing the entire path, stop
137              * That's a failure case, we quit here
138              */
139             if (TargetPath.Length == 0)
140             {
141                 ObpReleaseLookupContext(&LookupContext);
142                 return;
143             }
144 
145 
146             /*
147              * Make sure a deadlock does not occur as an exclusive lock on a pushlock
148              * would have already taken one in ObpLookupObjectName() on the parent
149              * directory where the symlink is being created [ObInsertObject()].
150              * Prevent recursive locking by faking lock state in the lookup context
151              * when the current directory is same as the parent directory where
152              * the symlink is being created. If the lock state is not faked,
153              * ObpLookupEntryDirectory() will try to get a recursive lock on the
154              * pushlock and hang. For e.g. this happens when a substed drive is pointed to
155              * another substed drive.
156              */
157             if (DirectoryObject == NameDirectory)
158             {
159                 DirectoryLocked = LookupContext.DirectoryLocked;
160                 LookupContext.DirectoryLocked = TRUE;
161             }
162             else
163             {
164                 DirectoryLocked = FALSE;
165             }
166 
167             Object = ObpLookupEntryDirectory(DirectoryObject,
168                                              &TargetPath,
169                                              0,
170                                              FALSE,
171                                              &LookupContext);
172 
173             /* Locking was faked, undo it now */
174             if (DirectoryObject == NameDirectory)
175             {
176                 LookupContext.DirectoryLocked = DirectoryLocked;
177             }
178 
179             /* Lookup failed, stop */
180             if (Object == NULL)
181             {
182                 break;
183             }
184 
185             /* If we don't have a directory object, we'll have to handle the object */
186             if (OBJECT_TO_OBJECT_HEADER(Object)->Type != ObpDirectoryObjectType)
187             {
188                 /* If that's not a symbolic link, stop here, nothing to do */
189                 if (OBJECT_TO_OBJECT_HEADER(Object)->Type != ObpSymbolicLinkObjectType ||
190                     (((POBJECT_SYMBOLIC_LINK)Object)->DosDeviceDriveIndex != 0))
191                 {
192                     break;
193                 }
194 
195                 /* We're out of reparse attempts */
196                 if (MaxReparse == 0)
197                 {
198                     Object = NULL;
199                     break;
200                 }
201 
202                 --MaxReparse;
203 
204                 /* Symlink points to another initialized symlink, ask caller to reparse */
205                 DirectoryObject = ObpRootDirectoryObject;
206 
207                 LocalTarget = ((POBJECT_SYMBOLIC_LINK)Object)->LinkTarget;
208 
209                 goto ReparseTargetPath;
210             }
211 
212             /* Make this current directory, and continue search */
213             DirectoryObject = Object;
214         }
215     }
216 
217     DeviceMap = NULL;
218     /* That's a drive letter, find a suitable device map */
219     if (SymbolicLink->DosDeviceDriveIndex != 0)
220     {
221         ObjectHeader = OBJECT_TO_OBJECT_HEADER(SymbolicLink);
222         ObjectNameInfo = ObpReferenceNameInfo(ObjectHeader);
223         if (ObjectNameInfo != NULL)
224         {
225             if (ObjectNameInfo->Directory != NULL)
226             {
227                 DeviceMap = ObjectNameInfo->Directory->DeviceMap;
228             }
229 
230             ObpDereferenceNameInfo(ObjectNameInfo);
231         }
232     }
233 
234     /* If we were asked to delete the symlink */
235     if (DeleteLink)
236     {
237         /* Zero its target */
238         RtlInitUnicodeString(&SymbolicLink->LinkTargetRemaining, NULL);
239 
240         /* If we had a target objected, dereference it */
241         if (SymbolicLink->LinkTargetObject != NULL)
242         {
243             ObDereferenceObject(SymbolicLink->LinkTargetObject);
244             SymbolicLink->LinkTargetObject = NULL;
245         }
246 
247         /* If it was a drive letter */
248         if (DeviceMap != NULL)
249         {
250             /* Acquire the device map lock */
251             KeAcquireGuardedMutex(&ObpDeviceMapLock);
252 
253             /* Remove the drive entry */
254             DeviceMap->DriveType[SymbolicLink->DosDeviceDriveIndex - 1] =
255                 DOSDEVICE_DRIVE_UNKNOWN;
256             DeviceMap->DriveMap &=
257                 ~(1 << (SymbolicLink->DosDeviceDriveIndex - 1));
258 
259             /* Release the device map lock */
260             KeReleaseGuardedMutex(&ObpDeviceMapLock);
261 
262             /* Reset the drive index, valid drive index starts from 1 */
263             SymbolicLink->DosDeviceDriveIndex = 0;
264         }
265     }
266     else
267     {
268         DriveType = DOSDEVICE_DRIVE_CALCULATE;
269 
270         /* If we have a drive letter and a pointer device object */
271         if (Object != NULL && SymbolicLink->DosDeviceDriveIndex != 0 &&
272             OBJECT_TO_OBJECT_HEADER(Object)->Type == IoDeviceObjectType)
273         {
274             /* Calculate the drive type */
275             switch(((PDEVICE_OBJECT)Object)->DeviceType)
276             {
277                 case FILE_DEVICE_VIRTUAL_DISK:
278                     DriveType = DOSDEVICE_DRIVE_RAMDISK;
279                     break;
280                 case FILE_DEVICE_CD_ROM:
281                 case FILE_DEVICE_CD_ROM_FILE_SYSTEM:
282                     DriveType = DOSDEVICE_DRIVE_CDROM;
283                     break;
284                 case FILE_DEVICE_DISK:
285                 case FILE_DEVICE_DISK_FILE_SYSTEM:
286                 case FILE_DEVICE_FILE_SYSTEM:
287                     if (((PDEVICE_OBJECT)Object)->Characteristics & FILE_REMOVABLE_MEDIA)
288                         DriveType = DOSDEVICE_DRIVE_REMOVABLE;
289                     else
290                         DriveType = DOSDEVICE_DRIVE_FIXED;
291                     break;
292                 case FILE_DEVICE_NETWORK:
293                 case FILE_DEVICE_NETWORK_FILE_SYSTEM:
294                     DriveType = DOSDEVICE_DRIVE_REMOTE;
295                     break;
296                 default:
297                     DPRINT1("Device Type %lu for %wZ is not known or unhandled\n",
298                             ((PDEVICE_OBJECT)Object)->DeviceType,
299                             &SymbolicLink->LinkTarget);
300                     DriveType = DOSDEVICE_DRIVE_UNKNOWN;
301             }
302         }
303 
304         /* Add a new drive entry */
305         if (DeviceMap != NULL)
306         {
307             /* Acquire the device map lock */
308             KeAcquireGuardedMutex(&ObpDeviceMapLock);
309 
310             DeviceMap->DriveType[SymbolicLink->DosDeviceDriveIndex - 1] =
311                 (UCHAR)DriveType;
312             DeviceMap->DriveMap |=
313                 1 << (SymbolicLink->DosDeviceDriveIndex - 1);
314 
315             /* Release the device map lock */
316             KeReleaseGuardedMutex(&ObpDeviceMapLock);
317         }
318     }
319 
320     /* Cleanup */
321     ObpReleaseLookupContext(&LookupContext);
322 }
323 
324 VOID
325 NTAPI
326 ObpDeleteSymbolicLinkName(IN POBJECT_SYMBOLIC_LINK SymbolicLink)
327 {
328     /* Just call the helper */
329     ObpProcessDosDeviceSymbolicLink(SymbolicLink, TRUE);
330 }
331 
332 VOID
333 NTAPI
334 ObpCreateSymbolicLinkName(IN POBJECT_SYMBOLIC_LINK SymbolicLink)
335 {
336     WCHAR UpperDrive;
337     POBJECT_HEADER ObjectHeader;
338     POBJECT_HEADER_NAME_INFO ObjectNameInfo;
339 
340     /* Get header data */
341     ObjectHeader = OBJECT_TO_OBJECT_HEADER(SymbolicLink);
342     ObjectNameInfo = ObpReferenceNameInfo(ObjectHeader);
343 
344     /* No name info, nothing to create */
345     if (ObjectNameInfo == NULL)
346     {
347         return;
348     }
349 
350     /* If we have a device map, look for creating a letter based drive */
351     if (ObjectNameInfo->Directory != NULL &&
352         ObjectNameInfo->Directory->DeviceMap != NULL)
353     {
354         /* Is it a drive letter based name? */
355         if (ObjectNameInfo->Name.Length == 2 * sizeof(WCHAR))
356         {
357             if (ObjectNameInfo->Name.Buffer[1] == L':')
358             {
359                 UpperDrive = RtlUpcaseUnicodeChar(ObjectNameInfo->Name.Buffer[0]);
360                 if (UpperDrive >= L'A' && UpperDrive <= L'Z')
361                 {
362                     /* Compute its index (it's 1 based - 0 means no letter) */
363                     SymbolicLink->DosDeviceDriveIndex = UpperDrive - (L'A' - 1);
364                 }
365             }
366         }
367 
368         /* Call the helper */
369         ObpProcessDosDeviceSymbolicLink(SymbolicLink, FALSE);
370     }
371 
372     /* We're done */
373     ObpDereferenceNameInfo(ObjectNameInfo);
374 }
375 
376 /*++
377 * @name ObpDeleteSymbolicLink
378 *
379 *     The ObpDeleteSymbolicLink routine <FILLMEIN>
380 *
381 * @param ObjectBody
382 *        <FILLMEIN>
383 *
384 * @return None.
385 *
386 * @remarks None.
387 *
388 *--*/
389 VOID
390 NTAPI
391 ObpDeleteSymbolicLink(PVOID ObjectBody)
392 {
393     POBJECT_SYMBOLIC_LINK SymlinkObject = (POBJECT_SYMBOLIC_LINK)ObjectBody;
394 
395     /* Make sure that the symbolic link has a name */
396     if (SymlinkObject->LinkTarget.Buffer)
397     {
398         /* Free the name */
399         ExFreePool(SymlinkObject->LinkTarget.Buffer);
400         SymlinkObject->LinkTarget.Buffer = NULL;
401     }
402 }
403 
404 /*++
405 * @name ObpParseSymbolicLink
406 *
407 *     The ObpParseSymbolicLink routine <FILLMEIN>
408 *
409 * @param Object
410 *        <FILLMEIN>
411 *
412 * @param NextObject
413 *        <FILLMEIN>
414 *
415 * @param FullPath
416 *        <FILLMEIN>
417 *
418 * @param RemainingPath
419 *        <FILLMEIN>
420 *
421 * @param Attributes
422 *        <FILLMEIN>
423 *
424 * @return STATUS_SUCCESS or appropriate error value.
425 *
426 * @remarks None.
427 *
428 *--*/
429 NTSTATUS
430 NTAPI
431 ObpParseSymbolicLink(IN PVOID ParsedObject,
432                      IN PVOID ObjectType,
433                      IN OUT PACCESS_STATE AccessState,
434                      IN KPROCESSOR_MODE AccessMode,
435                      IN ULONG Attributes,
436                      IN OUT PUNICODE_STRING FullPath,
437                      IN OUT PUNICODE_STRING RemainingName,
438                      IN OUT PVOID Context OPTIONAL,
439                      IN PSECURITY_QUALITY_OF_SERVICE SecurityQos OPTIONAL,
440                      OUT PVOID *NextObject)
441 {
442     POBJECT_SYMBOLIC_LINK SymlinkObject = (POBJECT_SYMBOLIC_LINK)ParsedObject;
443     PUNICODE_STRING TargetPath;
444     PWSTR NewTargetPath;
445     ULONG LengthUsed, MaximumLength, TempLength;
446     NTSTATUS Status;
447     PAGED_CODE();
448 
449     /* Assume failure */
450     *NextObject = NULL;
451 
452     /* Check if we're out of name to parse */
453     if (!RemainingName->Length)
454     {
455         /* Check if we got an object type */
456         if (ObjectType)
457         {
458             /* Reference the object only */
459             Status = ObReferenceObjectByPointer(ParsedObject,
460                                                 0,
461                                                 ObjectType,
462                                                 AccessMode);
463             if (NT_SUCCESS(Status))
464             {
465                 /* Return it */
466                 *NextObject = ParsedObject;
467             }
468 
469             if ((NT_SUCCESS(Status)) || (Status != STATUS_OBJECT_TYPE_MISMATCH))
470             {
471                 /* Fail */
472                 return Status;
473             }
474         }
475     }
476     else if (RemainingName->Buffer[0] != OBJ_NAME_PATH_SEPARATOR)
477     {
478         /* Symbolic links must start with a backslash */
479         return STATUS_OBJECT_TYPE_MISMATCH;
480     }
481 
482     /* Check if this symlink is bound to a specific object */
483     if (SymlinkObject->LinkTargetObject)
484     {
485         /* No name to reparse, directly reparse the object */
486         if (!SymlinkObject->LinkTargetRemaining.Length)
487         {
488             *NextObject = SymlinkObject->LinkTargetObject;
489             return STATUS_REPARSE_OBJECT;
490         }
491 
492         TempLength = SymlinkObject->LinkTargetRemaining.Length;
493         /* The target and remaining names aren't empty, so check for slashes */
494         if (SymlinkObject->LinkTargetRemaining.Buffer[TempLength / sizeof(WCHAR) - 1] == OBJ_NAME_PATH_SEPARATOR &&
495             RemainingName->Buffer[0] == OBJ_NAME_PATH_SEPARATOR)
496         {
497             /* Reduce the length by one to cut off the extra '\' */
498             TempLength -= sizeof(OBJ_NAME_PATH_SEPARATOR);
499         }
500 
501         /* Calculate the new length */
502         LengthUsed = TempLength + RemainingName->Length;
503         LengthUsed += (sizeof(WCHAR) * (RemainingName->Buffer - FullPath->Buffer));
504 
505         /* Check if it's not too much */
506         if (LengthUsed > 0xFFF0)
507             return STATUS_NAME_TOO_LONG;
508 
509         /* If FullPath is enough, use it */
510         if (FullPath->MaximumLength > LengthUsed)
511         {
512             /* Update remaining length if appropriate */
513             if (RemainingName->Length)
514             {
515                 RtlMoveMemory((PVOID)((ULONG_PTR)RemainingName->Buffer + TempLength),
516                               RemainingName->Buffer,
517                               RemainingName->Length);
518             }
519 
520             /* And move the target object name */
521             RtlCopyMemory(RemainingName->Buffer,
522                           SymlinkObject->LinkTargetRemaining.Buffer,
523                           TempLength);
524 
525             /* Finally update the full path with what we parsed */
526             FullPath->Length += SymlinkObject->LinkTargetRemaining.Length;
527             RemainingName->Length += SymlinkObject->LinkTargetRemaining.Length;
528             RemainingName->MaximumLength += RemainingName->Length + sizeof(WCHAR);
529             FullPath->Buffer[FullPath->Length / sizeof(WCHAR)] = UNICODE_NULL;
530 
531             /* And reparse */
532             *NextObject = SymlinkObject->LinkTargetObject;
533             return STATUS_REPARSE_OBJECT;
534         }
535 
536         /* FullPath is not enough, we'll have to reallocate */
537         MaximumLength = LengthUsed + sizeof(WCHAR);
538         NewTargetPath = ExAllocatePoolWithTag(NonPagedPool,
539                                               MaximumLength,
540                                               OB_NAME_TAG);
541         if (!NewTargetPath) return STATUS_INSUFFICIENT_RESOURCES;
542 
543         /* Copy path begin */
544         RtlCopyMemory(NewTargetPath,
545                       FullPath->Buffer,
546                       sizeof(WCHAR) * (RemainingName->Buffer - FullPath->Buffer));
547 
548         /* Copy path end (if any) */
549         if (RemainingName->Length)
550         {
551             RtlCopyMemory((PVOID)((ULONG_PTR)&NewTargetPath[RemainingName->Buffer - FullPath->Buffer] + TempLength),
552                           RemainingName->Buffer,
553                           RemainingName->Length);
554         }
555 
556         /* And finish path with bound object */
557         RtlCopyMemory(&NewTargetPath[RemainingName->Buffer - FullPath->Buffer],
558                       SymlinkObject->LinkTargetRemaining.Buffer,
559                       TempLength);
560 
561         /* Free old buffer */
562         ExFreePool(FullPath->Buffer);
563 
564         /* Set new buffer in FullPath */
565         FullPath->Buffer = NewTargetPath;
566         FullPath->MaximumLength = MaximumLength;
567         FullPath->Length = LengthUsed;
568 
569         /* Update remaining with what we handled */
570         RemainingName->Length = LengthUsed + (ULONG_PTR)NewTargetPath - (ULONG_PTR)&NewTargetPath[RemainingName->Buffer - FullPath->Buffer];
571         RemainingName->Buffer = &NewTargetPath[RemainingName->Buffer - FullPath->Buffer];
572         RemainingName->MaximumLength = RemainingName->Length + sizeof(WCHAR);
573 
574         /* Reparse! */
575         *NextObject = SymlinkObject->LinkTargetObject;
576         return STATUS_REPARSE_OBJECT;
577     }
578 
579     /* Set the target path and length */
580     TargetPath = &SymlinkObject->LinkTarget;
581     TempLength = TargetPath->Length;
582 
583     /*
584      * Strip off the extra trailing '\', if we don't do this we will end up
585      * adding a extra '\' between TargetPath and RemainingName
586      * causing caller's like ObpLookupObjectName() to fail.
587      */
588     if (TempLength && RemainingName->Length)
589     {
590         /* The target and remaining names aren't empty, so check for slashes */
591         if ((TargetPath->Buffer[TempLength / sizeof(WCHAR) - 1] ==
592             OBJ_NAME_PATH_SEPARATOR) &&
593             (RemainingName->Buffer[0] == OBJ_NAME_PATH_SEPARATOR))
594         {
595             /* Reduce the length by one to cut off the extra '\' */
596             TempLength -= sizeof(OBJ_NAME_PATH_SEPARATOR);
597         }
598     }
599 
600     /* Calculate the new length */
601     LengthUsed = TempLength + RemainingName->Length;
602 
603     /* Check if it's not too much */
604     if (LengthUsed > 0xFFF0)
605         return STATUS_NAME_TOO_LONG;
606 
607     /* Optimization: check if the new name is shorter */
608     if (FullPath->MaximumLength <= LengthUsed)
609     {
610         /* It's not, allocate a new one */
611         MaximumLength = LengthUsed + sizeof(WCHAR);
612         NewTargetPath = ExAllocatePoolWithTag(NonPagedPool,
613                                               MaximumLength,
614                                               OB_NAME_TAG);
615         if (!NewTargetPath) return STATUS_INSUFFICIENT_RESOURCES;
616     }
617     else
618     {
619         /* It is! Reuse the name... */
620         MaximumLength = FullPath->MaximumLength;
621         NewTargetPath = FullPath->Buffer;
622     }
623 
624     /* Make sure we have a length */
625     if (RemainingName->Length)
626     {
627         /* Copy the new path */
628         RtlMoveMemory((PVOID)((ULONG_PTR)NewTargetPath + TempLength),
629                       RemainingName->Buffer,
630                       RemainingName->Length);
631     }
632 
633     /* Copy the target path and null-terminate it */
634     RtlCopyMemory(NewTargetPath, TargetPath->Buffer, TempLength);
635     NewTargetPath[LengthUsed / sizeof(WCHAR)] = UNICODE_NULL;
636 
637     /* If the optimization didn't work, free the old buffer */
638     if (NewTargetPath != FullPath->Buffer) ExFreePool(FullPath->Buffer);
639 
640     /* Update the path values */
641     FullPath->Length = (USHORT)LengthUsed;
642     FullPath->MaximumLength = (USHORT)MaximumLength;
643     FullPath->Buffer = NewTargetPath;
644 
645     /* Tell the parse routine to start reparsing */
646     return STATUS_REPARSE;
647 }
648 
649 /* PUBLIC FUNCTIONS **********************************************************/
650 
651 /*++
652 * @name NtCreateSymbolicLinkObject
653 * @implemented NT4
654 *
655 *     The NtCreateSymbolicLinkObject opens or creates a symbolic link object.
656 *
657 * @param LinkHandle
658 *        Variable which receives the symlink handle.
659 *
660 * @param DesiredAccess
661 *        Desired access to the symlink.
662 *
663 * @param ObjectAttributes
664 *        Structure describing the symlink.
665 *
666 * @param LinkTarget
667 *        Unicode string defining the symlink's target
668 *
669 * @return STATUS_SUCCESS or appropriate error value.
670 *
671 * @remarks None.
672 *
673 *--*/
674 NTSTATUS
675 NTAPI
676 NtCreateSymbolicLinkObject(OUT PHANDLE LinkHandle,
677                            IN ACCESS_MASK DesiredAccess,
678                            IN POBJECT_ATTRIBUTES ObjectAttributes,
679                            IN PUNICODE_STRING LinkTarget)
680 {
681     HANDLE hLink;
682     POBJECT_SYMBOLIC_LINK SymbolicLink;
683     UNICODE_STRING CapturedLinkTarget;
684     KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
685     NTSTATUS Status;
686     PAGED_CODE();
687 
688     /* Check if we need to probe parameters */
689     if (PreviousMode != KernelMode)
690     {
691         _SEH2_TRY
692         {
693             /* Probe the target */
694             CapturedLinkTarget = ProbeForReadUnicodeString(LinkTarget);
695             ProbeForRead(CapturedLinkTarget.Buffer,
696                          CapturedLinkTarget.MaximumLength,
697                          sizeof(WCHAR));
698 
699             /* Probe the return handle */
700             ProbeForWriteHandle(LinkHandle);
701         }
702         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
703         {
704             /* Return the exception code */
705             _SEH2_YIELD(return _SEH2_GetExceptionCode());
706         }
707         _SEH2_END;
708     }
709     else
710     {
711         /* No need to capture */
712         CapturedLinkTarget = *LinkTarget;
713     }
714 
715     /* Check if the maximum length is odd */
716     if (CapturedLinkTarget.MaximumLength % sizeof(WCHAR))
717     {
718         /* Round it down */
719         CapturedLinkTarget.MaximumLength =
720             (USHORT)ALIGN_DOWN(CapturedLinkTarget.MaximumLength, WCHAR);
721     }
722 
723     /* Fail if the length is odd, or if the maximum is smaller or 0 */
724     if ((CapturedLinkTarget.Length % sizeof(WCHAR)) ||
725         (CapturedLinkTarget.MaximumLength < CapturedLinkTarget.Length) ||
726         !(CapturedLinkTarget.MaximumLength))
727     {
728         /* This message is displayed on the debugger in Windows */
729         DbgPrint("OB: Invalid symbolic link target - %wZ\n",
730                  &CapturedLinkTarget);
731         return STATUS_INVALID_PARAMETER;
732     }
733 
734     /* Create the object */
735     Status = ObCreateObject(PreviousMode,
736                             ObpSymbolicLinkObjectType,
737                             ObjectAttributes,
738                             PreviousMode,
739                             NULL,
740                             sizeof(OBJECT_SYMBOLIC_LINK),
741                             0,
742                             0,
743                             (PVOID*)&SymbolicLink);
744     if (NT_SUCCESS(Status))
745     {
746         /* Success! Fill in the creation time immediately */
747         KeQuerySystemTime(&SymbolicLink->CreationTime);
748 
749         /* Setup the target name */
750         SymbolicLink->LinkTarget.Length = CapturedLinkTarget.Length;
751         SymbolicLink->LinkTarget.MaximumLength = CapturedLinkTarget.MaximumLength;
752         SymbolicLink->LinkTarget.Buffer =
753             ExAllocatePoolWithTag(PagedPool,
754                                   CapturedLinkTarget.MaximumLength,
755                                   TAG_SYMLINK_TARGET);
756         if (!SymbolicLink->LinkTarget.Buffer)
757         {
758             /* Dereference the symbolic link object and fail */
759             ObDereferenceObject(SymbolicLink);
760             return STATUS_NO_MEMORY;
761         }
762 
763         /* Copy it */
764         _SEH2_TRY
765         {
766             RtlCopyMemory(SymbolicLink->LinkTarget.Buffer,
767                           CapturedLinkTarget.Buffer,
768                           CapturedLinkTarget.MaximumLength);
769         }
770         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
771         {
772             ObDereferenceObject(SymbolicLink);
773             _SEH2_YIELD(return _SEH2_GetExceptionCode());
774         }
775         _SEH2_END;
776 
777         /* Initialize the remaining name, dos drive index and target object */
778         SymbolicLink->LinkTargetObject = NULL;
779         SymbolicLink->DosDeviceDriveIndex = 0;
780         RtlInitUnicodeString(&SymbolicLink->LinkTargetRemaining, NULL);
781 
782         /* Insert it into the object tree */
783         Status = ObInsertObject(SymbolicLink,
784                                 NULL,
785                                 DesiredAccess,
786                                 0,
787                                 NULL,
788                                 &hLink);
789         if (NT_SUCCESS(Status))
790         {
791             _SEH2_TRY
792             {
793                 /* Return the handle to caller */
794                 *LinkHandle = hLink;
795             }
796             _SEH2_EXCEPT(ExSystemExceptionFilter())
797             {
798                 /* Get exception code */
799                 Status = _SEH2_GetExceptionCode();
800             }
801             _SEH2_END;
802         }
803     }
804 
805     /* Return status to caller */
806     return Status;
807 }
808 
809 /*++
810 * @name NtOpenSymbolicLinkObject
811 * @implemented NT4
812 *
813 *     The NtOpenSymbolicLinkObject opens a symbolic link object.
814 *
815 * @param LinkHandle
816 *        Variable which receives the symlink handle.
817 *
818 * @param DesiredAccess
819 *        Desired access to the symlink.
820 *
821 * @param ObjectAttributes
822 *        Structure describing the symlink.
823 *
824 * @return STATUS_SUCCESS or appropriate error value.
825 *
826 * @remarks None.
827 *
828 *--*/
829 NTSTATUS
830 NTAPI
831 NtOpenSymbolicLinkObject(OUT PHANDLE LinkHandle,
832                          IN ACCESS_MASK DesiredAccess,
833                          IN POBJECT_ATTRIBUTES ObjectAttributes)
834 {
835     HANDLE hLink;
836     KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
837     NTSTATUS Status;
838     PAGED_CODE();
839 
840     /* Check if we need to probe parameters */
841     if (PreviousMode != KernelMode)
842     {
843         _SEH2_TRY
844         {
845             /* Probe the return handle */
846             ProbeForWriteHandle(LinkHandle);
847         }
848         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
849         {
850             /* Return the exception code */
851             _SEH2_YIELD(return _SEH2_GetExceptionCode());
852         }
853         _SEH2_END;
854     }
855 
856     /* Open the object */
857     Status = ObOpenObjectByName(ObjectAttributes,
858                                 ObpSymbolicLinkObjectType,
859                                 PreviousMode,
860                                 NULL,
861                                 DesiredAccess,
862                                 NULL,
863                                 &hLink);
864 
865     _SEH2_TRY
866     {
867         /* Return the handle to caller */
868         *LinkHandle = hLink;
869     }
870     _SEH2_EXCEPT(ExSystemExceptionFilter())
871     {
872         /* Get exception code */
873         Status = _SEH2_GetExceptionCode();
874     }
875     _SEH2_END;
876 
877     /* Return status to caller */
878     return Status;
879 }
880 
881 /*++
882 * @name NtQuerySymbolicLinkObject
883 * @implemented NT4
884 *
885 *     The NtQuerySymbolicLinkObject queries a symbolic link object.
886 *
887 * @param LinkHandle
888 *        Symlink handle to query
889 *
890 * @param LinkTarget
891 *        Unicode string defining the symlink's target
892 *
893 * @param ResultLength
894 *        Caller supplied storage for the number of bytes written (or NULL).
895 *
896 * @return STATUS_SUCCESS or appropriate error value.
897 *
898 * @remarks None.
899 *
900 *--*/
901 NTSTATUS
902 NTAPI
903 NtQuerySymbolicLinkObject(IN HANDLE LinkHandle,
904                           OUT PUNICODE_STRING LinkTarget,
905                           OUT PULONG ResultLength OPTIONAL)
906 {
907     UNICODE_STRING SafeLinkTarget = { 0, 0, NULL };
908     POBJECT_SYMBOLIC_LINK SymlinkObject;
909     KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
910     NTSTATUS Status;
911     ULONG LengthUsed;
912     PAGED_CODE();
913 
914     if (PreviousMode != KernelMode)
915     {
916         _SEH2_TRY
917         {
918             /* Probe the unicode string for read and write */
919             ProbeForWriteUnicodeString(LinkTarget);
920 
921             /* Probe the unicode string's buffer for write */
922             SafeLinkTarget = *LinkTarget;
923             ProbeForWrite(SafeLinkTarget.Buffer,
924                           SafeLinkTarget.MaximumLength,
925                           sizeof(WCHAR));
926 
927             /* Probe the return length */
928             if (ResultLength) ProbeForWriteUlong(ResultLength);
929         }
930         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
931         {
932             /* Return the exception code */
933             _SEH2_YIELD(return _SEH2_GetExceptionCode());
934         }
935         _SEH2_END;
936     }
937     else
938     {
939         /* No need to probe */
940         SafeLinkTarget = *LinkTarget;
941     }
942 
943     /* Reference the object */
944     Status = ObReferenceObjectByHandle(LinkHandle,
945                                        SYMBOLIC_LINK_QUERY,
946                                        ObpSymbolicLinkObjectType,
947                                        PreviousMode,
948                                        (PVOID *)&SymlinkObject,
949                                        NULL);
950     if (NT_SUCCESS(Status))
951     {
952         /* Lock the object */
953         ObpAcquireObjectLock(OBJECT_TO_OBJECT_HEADER(SymlinkObject));
954 
955         /*
956          * So here's the thing: If you specify a return length, then the
957          * implementation will use the maximum length. If you don't, then
958          * it will use the length.
959          */
960         LengthUsed = ResultLength ? SymlinkObject->LinkTarget.MaximumLength :
961                                     SymlinkObject->LinkTarget.Length;
962 
963         /* Enter SEH so we can safely copy */
964         _SEH2_TRY
965         {
966             /* Make sure our buffer will fit */
967             if (LengthUsed <= SafeLinkTarget.MaximumLength)
968             {
969                 /* Copy the buffer */
970                 RtlCopyMemory(SafeLinkTarget.Buffer,
971                               SymlinkObject->LinkTarget.Buffer,
972                               LengthUsed);
973 
974                 /* Copy the new length */
975                 LinkTarget->Length = SymlinkObject->LinkTarget.Length;
976             }
977             else
978             {
979                 /* Otherwise set the failure status */
980                 Status = STATUS_BUFFER_TOO_SMALL;
981             }
982 
983             /* In both cases, check if the required length was requested */
984             if (ResultLength)
985             {
986                 /* Then return it */
987                 *ResultLength = SymlinkObject->LinkTarget.MaximumLength;
988             }
989         }
990         _SEH2_EXCEPT(ExSystemExceptionFilter())
991         {
992             /* Get the error code */
993             Status = _SEH2_GetExceptionCode();
994         }
995         _SEH2_END;
996 
997         /* Unlock and dereference the object */
998         ObpReleaseObjectLock(OBJECT_TO_OBJECT_HEADER(SymlinkObject));
999         ObDereferenceObject(SymlinkObject);
1000     }
1001 
1002     /* Return query status */
1003     return Status;
1004 }
1005 
1006 /* EOF */
1007