xref: /reactos/sdk/lib/drivers/ntoskrnl_vista/fsrtl.c (revision 2faee116)
1 /*
2  * PROJECT:         ReactOS Kernel - Vista+ APIs
3  * LICENSE:         LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later)
4  * FILE:            lib/drivers/ntoskrnl_vista/fsrtl.c
5  * PURPOSE:         FsRtl functions of Vista+
6  * PROGRAMMERS:     Pierre Schweitzer <pierre@reactos.org>
7  */
8 
9 #include <ntdef.h>
10 #include <ntifs.h>
11 
12 typedef struct _ECP_LIST
13 {
14     ULONG Signature;
15     ULONG Flags;
16     LIST_ENTRY EcpList;
17 } ECP_LIST, *PECP_LIST;
18 
19 typedef ULONG ECP_HEADER_FLAGS;
20 
21 typedef struct _ECP_HEADER
22 {
23     ULONG Signature;
24     ULONG Spare;
25     LIST_ENTRY ListEntry;
26     GUID EcpType;
27     PFSRTL_EXTRA_CREATE_PARAMETER_CLEANUP_CALLBACK CleanupCallback;
28     ECP_HEADER_FLAGS Flags;
29     ULONG Size;
30     PVOID ListAllocatedFrom;
31     PVOID Filter;
32 } ECP_HEADER, *PECP_HEADER;
33 
34 #define ECP_HEADER_SIZE (sizeof(ECP_HEADER))
35 
36 #define ECP_HEADER_TO_CONTEXT(H) ((PVOID)((ULONG_PTR)H + ECP_HEADER_SIZE))
37 #define ECP_CONTEXT_TO_HEADER(C) ((PECP_HEADER)((ULONG_PTR)C - ECP_HEADER_SIZE))
38 
39 NTKERNELAPI
40 NTSTATUS
41 NTAPI
FsRtlRemoveDotsFromPath(IN PWSTR OriginalString,IN USHORT PathLength,OUT USHORT * NewLength)42 FsRtlRemoveDotsFromPath(IN PWSTR OriginalString,
43                         IN USHORT PathLength,
44                         OUT USHORT *NewLength)
45 {
46     USHORT Length, ReadPos, WritePos;
47 
48     Length = PathLength / sizeof(WCHAR);
49 
50     if (Length == 3 && OriginalString[0] == '\\' && OriginalString[1] == '.' && OriginalString[2] == '.')
51     {
52         return STATUS_IO_REPARSE_DATA_INVALID;
53     }
54 
55     if (Length == 2 && OriginalString[0] == '.' && OriginalString[1] == '.')
56     {
57         return STATUS_IO_REPARSE_DATA_INVALID;
58     }
59 
60     if (Length > 2 && OriginalString[0] == '.' && OriginalString[1] == '.' && OriginalString[2] == '\\')
61     {
62         return STATUS_IO_REPARSE_DATA_INVALID;
63     }
64 
65     for (ReadPos = 0, WritePos = 0; ReadPos < Length; ++WritePos)
66     {
67         for (; ReadPos > 0 && ReadPos < Length; ++ReadPos)
68         {
69             if (ReadPos < Length - 1 && OriginalString[ReadPos] == '\\' && OriginalString[ReadPos + 1] == '\\')
70             {
71                 continue;
72             }
73 
74             if (OriginalString[ReadPos] != '.')
75             {
76                 break;
77             }
78 
79             if (ReadPos == Length - 1)
80             {
81                 if (OriginalString[ReadPos - 1] == '\\')
82                 {
83                     if (WritePos > 1)
84                     {
85                         --WritePos;
86                     }
87 
88                     continue;
89                 }
90 
91                 OriginalString[WritePos] = '.';
92                 ++WritePos;
93                 continue;
94             }
95 
96             if (OriginalString[ReadPos + 1] == '\\')
97             {
98                 if (OriginalString[ReadPos - 1] != '\\')
99                 {
100                     OriginalString[WritePos] = '.';
101                     ++WritePos;
102                     continue;
103                 }
104             }
105             else
106             {
107                 if (OriginalString[ReadPos + 1] != '.' || OriginalString[ReadPos - 1] != '\\' ||
108                     ((ReadPos != Length - 2) && OriginalString[ReadPos + 2] != '\\'))
109                 {
110                     OriginalString[WritePos] = '.';
111                     ++WritePos;
112                     continue;
113                 }
114 
115                 for (WritePos -= 2; (SHORT)WritePos > 0 && OriginalString[WritePos] != '\\'; --WritePos);
116 
117                 if ((SHORT)WritePos < 0 || OriginalString[WritePos] != '\\')
118                 {
119                     return STATUS_IO_REPARSE_DATA_INVALID;
120                 }
121 
122                 if (WritePos == 0 && ReadPos == Length - 2)
123                 {
124                     WritePos = 1;
125                 }
126             }
127 
128             ++ReadPos;
129         }
130 
131         if (ReadPos >= Length)
132         {
133             break;
134         }
135 
136         OriginalString[WritePos] = OriginalString[ReadPos];
137         ++ReadPos;
138     }
139 
140     *NewLength = WritePos * sizeof(WCHAR);
141 
142     while (WritePos < Length)
143     {
144         OriginalString[WritePos++] = UNICODE_NULL;
145     }
146 
147     return STATUS_SUCCESS;
148 }
149 
150 FORCEINLINE
151 BOOLEAN
IsNullGuid(IN PGUID Guid)152 IsNullGuid(IN PGUID Guid)
153 {
154     if (Guid->Data1 == 0 && Guid->Data2 == 0 && Guid->Data3 == 0 &&
155         ((ULONG *)Guid->Data4)[0] == 0 && ((ULONG *)Guid->Data4)[1] == 0)
156     {
157         return TRUE;
158     }
159 
160     return FALSE;
161 }
162 
163 FORCEINLINE
164 BOOLEAN
IsEven(IN USHORT Digit)165 IsEven(IN USHORT Digit)
166 {
167     return ((Digit & 1) != 1);
168 }
169 
170 NTKERNELAPI
171 NTSTATUS
172 NTAPI
FsRtlValidateReparsePointBuffer(IN ULONG BufferLength,IN PREPARSE_DATA_BUFFER ReparseBuffer)173 FsRtlValidateReparsePointBuffer(IN ULONG BufferLength,
174                                 IN PREPARSE_DATA_BUFFER ReparseBuffer)
175 {
176     USHORT DataLength;
177     ULONG ReparseTag;
178     PREPARSE_GUID_DATA_BUFFER GuidBuffer;
179 
180     /* Validate data size range */
181     if (BufferLength < REPARSE_DATA_BUFFER_HEADER_SIZE || BufferLength > MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
182     {
183         return STATUS_IO_REPARSE_DATA_INVALID;
184     }
185 
186     GuidBuffer = (PREPARSE_GUID_DATA_BUFFER)ReparseBuffer;
187     DataLength = ReparseBuffer->ReparseDataLength;
188     ReparseTag = ReparseBuffer->ReparseTag;
189 
190     /* Validate size consistency */
191     if (DataLength + REPARSE_DATA_BUFFER_HEADER_SIZE != BufferLength && DataLength + REPARSE_GUID_DATA_BUFFER_HEADER_SIZE != BufferLength)
192     {
193         return STATUS_IO_REPARSE_DATA_INVALID;
194     }
195 
196     /* REPARSE_DATA_BUFFER is reserved for MS tags */
197     if (DataLength + REPARSE_DATA_BUFFER_HEADER_SIZE == BufferLength && !IsReparseTagMicrosoft(ReparseTag))
198     {
199         return STATUS_IO_REPARSE_DATA_INVALID;
200     }
201 
202     /* If that a GUID data buffer, its GUID cannot be null, and it cannot contain a MS tag */
203     if (DataLength + REPARSE_GUID_DATA_BUFFER_HEADER_SIZE == BufferLength && ((!IsReparseTagMicrosoft(ReparseTag)
204         && IsNullGuid(&GuidBuffer->ReparseGuid)) || (ReparseTag == IO_REPARSE_TAG_MOUNT_POINT || ReparseTag == IO_REPARSE_TAG_SYMLINK)))
205     {
206         return STATUS_IO_REPARSE_DATA_INVALID;
207     }
208 
209     /* Check the data for MS non reserved tags */
210     if (!(ReparseTag & 0xFFF0000) && ReparseTag != IO_REPARSE_TAG_RESERVED_ZERO && ReparseTag != IO_REPARSE_TAG_RESERVED_ONE)
211     {
212         /* If that's a mount point, validate the MountPointReparseBuffer branch */
213         if (ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
214         {
215             /* We need information */
216             if (DataLength >= REPARSE_DATA_BUFFER_HEADER_SIZE)
217             {
218                 /* Substitue must be the first in row */
219                 if (!ReparseBuffer->MountPointReparseBuffer.SubstituteNameOffset)
220                 {
221                     /* Substitude must be null-terminated */
222                     if (ReparseBuffer->MountPointReparseBuffer.PrintNameOffset == ReparseBuffer->MountPointReparseBuffer.SubstituteNameLength + sizeof(UNICODE_NULL))
223                     {
224                         /* There must just be the Offset/Length fields + buffer + 2 null chars */
225                         if (DataLength == ReparseBuffer->MountPointReparseBuffer.PrintNameLength + ReparseBuffer->MountPointReparseBuffer.SubstituteNameLength + (FIELD_OFFSET(REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer) - FIELD_OFFSET(REPARSE_DATA_BUFFER, MountPointReparseBuffer.SubstituteNameOffset)) + 2 * sizeof(UNICODE_NULL))
226                         {
227                             return STATUS_SUCCESS;
228                         }
229                     }
230                 }
231             }
232         }
233         else
234         {
235 #define FIELDS_SIZE (FIELD_OFFSET(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer) - FIELD_OFFSET(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.SubstituteNameOffset))
236 
237             /* If that's not a symlink, accept the MS tag as it */
238             if (ReparseTag != IO_REPARSE_TAG_SYMLINK)
239             {
240                 return STATUS_SUCCESS;
241             }
242 
243             /* We need information */
244             if (DataLength >= FIELDS_SIZE)
245             {
246                 /* Validate lengths */
247                 if (ReparseBuffer->SymbolicLinkReparseBuffer.SubstituteNameLength && ReparseBuffer->SymbolicLinkReparseBuffer.PrintNameLength)
248                 {
249                     /* Validate unicode strings */
250                     if (IsEven(ReparseBuffer->SymbolicLinkReparseBuffer.SubstituteNameLength) && IsEven(ReparseBuffer->SymbolicLinkReparseBuffer.PrintNameLength) &&
251                         IsEven(ReparseBuffer->SymbolicLinkReparseBuffer.SubstituteNameOffset) && IsEven(ReparseBuffer->SymbolicLinkReparseBuffer.PrintNameOffset))
252                     {
253                         if ((DataLength + REPARSE_DATA_BUFFER_HEADER_SIZE >= ReparseBuffer->SymbolicLinkReparseBuffer.SubstituteNameOffset + ReparseBuffer->SymbolicLinkReparseBuffer.SubstituteNameLength + FIELDS_SIZE + REPARSE_DATA_BUFFER_HEADER_SIZE)
254                             && (DataLength + REPARSE_DATA_BUFFER_HEADER_SIZE >= ReparseBuffer->SymbolicLinkReparseBuffer.PrintNameLength + ReparseBuffer->SymbolicLinkReparseBuffer.PrintNameOffset + FIELDS_SIZE + REPARSE_DATA_BUFFER_HEADER_SIZE))
255                         {
256                             return STATUS_SUCCESS;
257                         }
258                     }
259                 }
260             }
261 #undef FIELDS_SIZE
262         }
263 
264         return STATUS_IO_REPARSE_DATA_INVALID;
265     }
266 
267     return STATUS_IO_REPARSE_TAG_INVALID;
268 }
269 
270 NTKERNELAPI
271 NTSTATUS
272 NTAPI
FsRtlGetEcpListFromIrp(IN PIRP Irp,OUT PECP_LIST * EcpList)273 FsRtlGetEcpListFromIrp(IN PIRP Irp,
274                        OUT PECP_LIST *EcpList)
275 {
276     /* Call Io */
277     return IoGetIrpExtraCreateParameter(Irp, EcpList);
278 }
279 
280 NTKERNELAPI
281 NTSTATUS
282 NTAPI
FsRtlGetNextExtraCreateParameter(IN PECP_LIST EcpList,IN PVOID CurrentEcpContext,OUT LPGUID NextEcpType OPTIONAL,OUT PVOID * NextEcpContext,OUT PULONG NextEcpContextSize OPTIONAL)283 FsRtlGetNextExtraCreateParameter(IN PECP_LIST EcpList,
284                                  IN PVOID CurrentEcpContext,
285                                  OUT LPGUID NextEcpType OPTIONAL,
286                                  OUT PVOID *NextEcpContext,
287                                  OUT PULONG NextEcpContextSize OPTIONAL)
288 {
289     PECP_HEADER CurrentEntry;
290 
291     /* If we have no context ... */
292     if (CurrentEcpContext == NULL)
293     {
294         if (IsListEmpty(&EcpList->EcpList))
295         {
296             goto FailEmpty;
297         }
298 
299         /* Simply consider first entry */
300         CurrentEntry = CONTAINING_RECORD(EcpList->EcpList.Flink, ECP_HEADER, ListEntry);
301     }
302     else
303     {
304         /* Otherwise, consider the entry matching the given context */
305         CurrentEntry = ECP_CONTEXT_TO_HEADER(CurrentEcpContext);
306 
307         /* Make sure we didn't reach the end */
308         if (&CurrentEntry->ListEntry == &EcpList->EcpList)
309         {
310             goto FailEmpty;
311         }
312     }
313 
314     /* We must have an entry */
315     if (CurrentEntry == NULL)
316     {
317         goto FailEmpty;
318     }
319 
320     /* If caller wants a context, give it */
321     if (NextEcpContext != NULL)
322     {
323         *NextEcpContext = ECP_HEADER_TO_CONTEXT(CurrentEntry);
324     }
325 
326     /* Same for its size (which the size minus the header overhead) */
327     if (NextEcpContextSize != NULL)
328     {
329          *NextEcpContextSize = CurrentEntry->Size - sizeof(ECP_HEADER);
330     }
331 
332     /* And copy the type if asked to */
333     if (NextEcpType != NULL)
334     {
335         RtlCopyMemory(NextEcpType, &CurrentEntry->EcpType, sizeof(GUID));
336     }
337 
338     /* Job done */
339     return STATUS_SUCCESS;
340 
341     /* Failure case: just zero everything */
342 FailEmpty:
343     if (NextEcpContext != NULL)
344     {
345         *NextEcpContext = NULL;
346     }
347 
348     if (NextEcpContextSize != NULL)
349     {
350         *NextEcpContextSize = 0;
351     }
352 
353     if (NextEcpType != NULL)
354     {
355         RtlZeroMemory(NextEcpType, sizeof(GUID));
356     }
357 
358     /* And return failure */
359     return STATUS_NOT_FOUND;
360 }
361 
362