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 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 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 165 IsEven(IN USHORT Digit) 166 { 167 return ((Digit & 1) != 1); 168 } 169 170 NTKERNELAPI 171 NTSTATUS 172 NTAPI 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 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 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