1 /*
2 * COPYRIGHT: See COPYRIGHT.TXT
3 * PROJECT: Ext2 File System Driver for Windows >= NT
4 * FILE: ea.c
5 * PROGRAMMER: Matt Wu <mattwu@163.com> Kaho Ng <ngkaho1234@gmail.com>
6 * HOMEPAGE: http://www.ext2fsd.com
7 * UPDATE HISTORY:
8 */
9
10 /* INCLUDES *****************************************************************/
11
12 #include "ext2fs.h"
13 #include <linux/ext4_xattr.h>
14
15 #ifdef ALLOC_PRAGMA
16 #pragma alloc_text(PAGE, Ext2QueryEa)
17 #pragma alloc_text(PAGE, Ext2SetEa)
18 #pragma alloc_text(PAGE, Ext2IsEaNameValid)
19 #endif
20
21 // Ea iterator
22 struct EaIterator {
23 // Return only an entry
24 BOOLEAN ReturnSingleEntry;
25
26 // Is the buffer overflowing?
27 BOOL OverFlow;
28
29 // FILE_FULL_EA_INFORMATION output buffer
30 PFILE_FULL_EA_INFORMATION FullEa;
31 PFILE_FULL_EA_INFORMATION LastFullEa;
32
33 // UserBuffer's size
34 ULONG UserBufferLength;
35
36 // Remaining UserBuffer's size
37 ULONG RemainingUserBufferLength;
38
39 // Start scanning from this EA
40 ULONG EaIndex;
41
42 // Next EA index returned by Ext2IterateAllEa
43 ULONG EaIndexCounter;
44 };
45
Ext2IterateAllEa(struct ext4_xattr_ref * xattr_ref,struct ext4_xattr_item * item,BOOL is_last)46 static int Ext2IterateAllEa(struct ext4_xattr_ref *xattr_ref, struct ext4_xattr_item *item, BOOL is_last)
47 {
48 struct EaIterator *pEaIterator = xattr_ref->iter_arg;
49 ULONG EaEntrySize = 4 + 1 + 1 + 2 + item->name_len + 1 + item->data_size;
50 ASSERT(pEaIterator);
51 if (!is_last && !pEaIterator->ReturnSingleEntry)
52 EaEntrySize = ALIGN_UP(EaEntrySize, ULONG);
53
54 // Start iteration from index specified
55 if (pEaIterator->EaIndexCounter < pEaIterator->EaIndex) {
56 pEaIterator->EaIndexCounter++;
57 return EXT4_XATTR_ITERATE_CONT;
58 }
59 pEaIterator->EaIndexCounter++;
60
61 if (EaEntrySize > pEaIterator->RemainingUserBufferLength) {
62 pEaIterator->OverFlow = TRUE;
63 return EXT4_XATTR_ITERATE_STOP;
64 }
65 pEaIterator->FullEa->NextEntryOffset = 0;
66 pEaIterator->FullEa->Flags = 0;
67 pEaIterator->FullEa->EaNameLength = (UCHAR)item->name_len;
68 pEaIterator->FullEa->EaValueLength = (USHORT)item->data_size;
69 RtlCopyMemory(&pEaIterator->FullEa->EaName[0],
70 item->name,
71 item->name_len);
72 RtlCopyMemory(&pEaIterator->FullEa->EaName[0] + item->name_len + 1,
73 item->data,
74 item->data_size);
75
76 // Link FullEa and LastFullEa together
77 if (pEaIterator->LastFullEa) {
78 pEaIterator->LastFullEa->NextEntryOffset = (ULONG)
79 ((PCHAR)pEaIterator->FullEa - (PCHAR)pEaIterator->LastFullEa);
80 }
81
82 pEaIterator->LastFullEa = pEaIterator->FullEa;
83 pEaIterator->FullEa = (PFILE_FULL_EA_INFORMATION)
84 ((PCHAR)pEaIterator->FullEa + EaEntrySize);
85 pEaIterator->RemainingUserBufferLength -= EaEntrySize;
86
87 if (pEaIterator->ReturnSingleEntry)
88 return EXT4_XATTR_ITERATE_STOP;
89
90 return EXT4_XATTR_ITERATE_CONT;
91 }
92
93 NTSTATUS
Ext2QueryEa(IN PEXT2_IRP_CONTEXT IrpContext)94 Ext2QueryEa (
95 IN PEXT2_IRP_CONTEXT IrpContext
96 )
97 {
98 PIRP Irp = NULL;
99 PIO_STACK_LOCATION IrpSp;
100
101 PDEVICE_OBJECT DeviceObject;
102
103 PEXT2_VCB Vcb = NULL;
104 PEXT2_FCB Fcb = NULL;
105 PEXT2_CCB Ccb = NULL;
106 PEXT2_MCB Mcb = NULL;
107
108 PUCHAR UserEaList;
109 ULONG UserEaListLength;
110 ULONG UserEaIndex;
111
112 BOOLEAN RestartScan;
113 BOOLEAN ReturnSingleEntry;
114 BOOLEAN IndexSpecified;
115
116 BOOLEAN MainResourceAcquired = FALSE;
117 BOOLEAN XattrRefAcquired = FALSE;
118
119 NTSTATUS Status = STATUS_UNSUCCESSFUL;
120
121 struct ext4_xattr_ref xattr_ref;
122 PCHAR UserBuffer;
123
124 ULONG UserBufferLength = 0;
125 ULONG RemainingUserBufferLength = 0;
126
127 PFILE_FULL_EA_INFORMATION FullEa, LastFullEa = NULL;
128
129 _SEH2_TRY {
130
131 Ccb = IrpContext->Ccb;
132 ASSERT(Ccb != NULL);
133 ASSERT((Ccb->Identifier.Type == EXT2CCB) &&
134 (Ccb->Identifier.Size == sizeof(EXT2_CCB)));
135 DeviceObject = IrpContext->DeviceObject;
136 Vcb = (PEXT2_VCB)DeviceObject->DeviceExtension;
137 Fcb = IrpContext->Fcb;
138 Mcb = Fcb->Mcb;
139 Irp = IrpContext->Irp;
140 IrpSp = IoGetCurrentIrpStackLocation(Irp);
141
142 Irp->IoStatus.Information = 0;
143
144 //
145 // Receive input parameter from caller
146 //
147 UserBuffer = Ext2GetUserBuffer(Irp);
148 if (!UserBuffer) {
149 Status = STATUS_INSUFFICIENT_RESOURCES;
150 _SEH2_LEAVE;
151 }
152 UserBufferLength = IrpSp->Parameters.QueryEa.Length;
153 RemainingUserBufferLength = UserBufferLength;
154 UserEaList = IrpSp->Parameters.QueryEa.EaList;
155 UserEaListLength = IrpSp->Parameters.QueryEa.EaListLength;
156 UserEaIndex = IrpSp->Parameters.QueryEa.EaIndex;
157 RestartScan = BooleanFlagOn(IrpSp->Flags, SL_RESTART_SCAN);
158 ReturnSingleEntry = BooleanFlagOn(IrpSp->Flags, SL_RETURN_SINGLE_ENTRY);
159 IndexSpecified = BooleanFlagOn(IrpSp->Flags, SL_INDEX_SPECIFIED);
160
161 if (!Mcb)
162 _SEH2_LEAVE;
163
164 //
165 // We do not allow multiple instance gaining EA access to the same file
166 //
167 if (!ExAcquireResourceExclusiveLite(
168 &Fcb->MainResource,
169 IsFlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT))) {
170 Status = STATUS_PENDING;
171 _SEH2_LEAVE;
172 }
173 MainResourceAcquired = TRUE;
174
175 Status = Ext2WinntError(ext4_fs_get_xattr_ref(IrpContext, Vcb, Fcb->Mcb, &xattr_ref));
176 if (!NT_SUCCESS(Status)) {
177 DbgPrint("ext4_fs_get_xattr_ref() failed!\n");
178 _SEH2_LEAVE;
179 }
180
181 FullEa = (PFILE_FULL_EA_INFORMATION)UserBuffer;
182
183 XattrRefAcquired = TRUE;
184
185 if (RemainingUserBufferLength)
186 RtlZeroMemory(FullEa, RemainingUserBufferLength);
187
188 if (UserEaList != NULL) {
189 int i = 0;
190 PFILE_GET_EA_INFORMATION GetEa;
191 for (GetEa = (PFILE_GET_EA_INFORMATION)&UserEaList[0];
192 GetEa < (PFILE_GET_EA_INFORMATION)((PUCHAR)UserEaList
193 + UserEaListLength);
194 GetEa = (GetEa->NextEntryOffset == 0
195 ? (PFILE_GET_EA_INFORMATION)MAXUINT_PTR
196 : (PFILE_GET_EA_INFORMATION)((PUCHAR)GetEa
197 + GetEa->NextEntryOffset))) {
198
199 size_t ItemSize;
200 OEM_STRING Str;
201 ULONG EaEntrySize;
202 BOOL is_last = !GetEa->NextEntryOffset;
203
204 Str.MaximumLength = Str.Length = GetEa->EaNameLength;
205 Str.Buffer = &GetEa->EaName[0];
206
207 //
208 // At the moment we only need to know whether the item exists
209 // and its size.
210 //
211 Status = Ext2WinntError(ext4_fs_get_xattr(&xattr_ref,
212 EXT4_XATTR_INDEX_USER,
213 Str.Buffer,
214 Str.Length,
215 NULL,
216 0,
217 &ItemSize));
218 if (!NT_SUCCESS(Status))
219 continue;
220
221 //
222 // We were not able to locate the name therefore we must
223 // dummy up a entry for the query. The needed Ea size is
224 // the size of the name + 4 (next entry offset) + 1 (flags)
225 // + 1 (name length) + 2 (value length) + the name length +
226 // 1 (null byte) + Data Size.
227 //
228 EaEntrySize = 4 + 1 + 1 + 2 + GetEa->EaNameLength + 1 + ItemSize;
229 if (!is_last)
230 EaEntrySize = ALIGN_UP(EaEntrySize, ULONG);
231
232 if (EaEntrySize > RemainingUserBufferLength) {
233
234 Status = i ? STATUS_BUFFER_OVERFLOW : STATUS_BUFFER_TOO_SMALL;
235 _SEH2_LEAVE;
236 }
237 FullEa->NextEntryOffset = 0;
238 FullEa->Flags = 0;
239 FullEa->EaNameLength = GetEa->EaNameLength;
240 FullEa->EaValueLength = (USHORT)ItemSize;
241 RtlCopyMemory(&FullEa->EaName[0],
242 &GetEa->EaName[0],
243 GetEa->EaNameLength);
244
245 //
246 // This query must succeed, or is guarenteed to succeed
247 // since we are only looking up
248 // an EA entry in a in-memory tree structure.
249 // Otherwise that means someone might be operating on
250 // the xattr_ref without acquiring Inode lock.
251 //
252 ASSERT(NT_SUCCESS(Ext2WinntError(
253 ext4_fs_get_xattr(&xattr_ref,
254 EXT4_XATTR_INDEX_USER,
255 Str.Buffer,
256 Str.Length,
257 &FullEa->EaName[0] + FullEa->EaNameLength + 1,
258 ItemSize,
259 &ItemSize
260 ))));
261 FullEa->EaValueLength = (USHORT)ItemSize;
262
263 // Link FullEa and LastFullEa together
264 if (LastFullEa)
265 LastFullEa->NextEntryOffset = (ULONG)((PCHAR)FullEa -
266 (PCHAR)LastFullEa);
267
268 LastFullEa = FullEa;
269 FullEa = (PFILE_FULL_EA_INFORMATION)
270 ((PCHAR)FullEa + EaEntrySize);
271 RemainingUserBufferLength -= EaEntrySize;
272 i++;
273 }
274 } else if (IndexSpecified) {
275 struct EaIterator EaIterator;
276 //
277 // The user supplied an index into the Ea list.
278 //
279 if (RemainingUserBufferLength)
280 RtlZeroMemory(FullEa, RemainingUserBufferLength);
281
282 EaIterator.OverFlow = FALSE;
283 EaIterator.RemainingUserBufferLength = UserBufferLength;
284 // In this case, return only an entry.
285 EaIterator.ReturnSingleEntry = TRUE;
286 EaIterator.FullEa = (PFILE_FULL_EA_INFORMATION)UserBuffer;
287 EaIterator.LastFullEa = NULL;
288 EaIterator.UserBufferLength = UserBufferLength;
289 EaIterator.EaIndex = UserEaIndex;
290 EaIterator.EaIndexCounter = 1;
291
292 xattr_ref.iter_arg = &EaIterator;
293 ext4_fs_xattr_iterate(&xattr_ref, Ext2IterateAllEa);
294
295 RemainingUserBufferLength = EaIterator.RemainingUserBufferLength;
296
297 Status = STATUS_SUCCESS;
298
299 // It seems that the item isn't found
300 if (RemainingUserBufferLength == UserBufferLength)
301 Status = STATUS_OBJECTID_NOT_FOUND;
302
303 if (EaIterator.OverFlow) {
304 if (RemainingUserBufferLength == UserBufferLength)
305 Status = STATUS_BUFFER_TOO_SMALL;
306 else
307 Status = STATUS_BUFFER_OVERFLOW;
308 }
309
310 } else {
311 struct EaIterator EaIterator;
312 //
313 // Else perform a simple scan, taking into account the restart
314 // flag and the position of the next Ea stored in the Ccb.
315 //
316 if (RestartScan)
317 Ccb->EaIndex = 1;
318
319 if (RemainingUserBufferLength)
320 RtlZeroMemory(FullEa, RemainingUserBufferLength);
321
322 EaIterator.OverFlow = FALSE;
323 EaIterator.RemainingUserBufferLength = UserBufferLength;
324 EaIterator.ReturnSingleEntry = ReturnSingleEntry;
325 EaIterator.FullEa = (PFILE_FULL_EA_INFORMATION)UserBuffer;
326 EaIterator.LastFullEa = NULL;
327 EaIterator.UserBufferLength = UserBufferLength;
328 EaIterator.EaIndex = Ccb->EaIndex;
329 EaIterator.EaIndexCounter = 1;
330
331 xattr_ref.iter_arg = &EaIterator;
332 ext4_fs_xattr_iterate(&xattr_ref, Ext2IterateAllEa);
333
334 RemainingUserBufferLength = EaIterator.RemainingUserBufferLength;
335
336 if (Ccb->EaIndex < EaIterator.EaIndexCounter)
337 Ccb->EaIndex = EaIterator.EaIndexCounter;
338
339 Status = STATUS_SUCCESS;
340
341 if (EaIterator.OverFlow) {
342 if (RemainingUserBufferLength == UserBufferLength)
343 Status = STATUS_BUFFER_TOO_SMALL;
344 else
345 Status = STATUS_BUFFER_OVERFLOW;
346 }
347
348 }
349 }
350 _SEH2_FINALLY {
351
352 if (XattrRefAcquired) {
353 if (!NT_SUCCESS(Status)) {
354 xattr_ref.dirty = FALSE;
355 ext4_fs_put_xattr_ref(&xattr_ref);
356 }
357 else
358 Status = Ext2WinntError(ext4_fs_put_xattr_ref(&xattr_ref));
359 }
360
361 if (MainResourceAcquired) {
362 ExReleaseResourceLite(&Fcb->MainResource);
363 }
364
365 if (NT_SUCCESS(Status)) {
366 Ext2NotifyReportChange(
367 IrpContext,
368 Vcb,
369 Mcb,
370 FILE_NOTIFY_CHANGE_EA,
371 FILE_ACTION_MODIFIED);
372 Irp->IoStatus.Information = UserBufferLength - RemainingUserBufferLength;
373 }
374
375 if (!_SEH2_AbnormalTermination()) {
376 if (Status == STATUS_PENDING || Status == STATUS_CANT_WAIT) {
377 Status = Ext2QueueRequest(IrpContext);
378 }
379 else {
380 Ext2CompleteIrpContext(IrpContext, Status);
381 }
382 }
383 } _SEH2_END;
384
385 return Status;
386 }
387
388 BOOLEAN
Ext2IsEaNameValid(IN OEM_STRING Name)389 Ext2IsEaNameValid(
390 IN OEM_STRING Name
391 )
392 {
393 ULONG Index;
394 UCHAR Char;
395
396 //
397 // Empty names are not valid
398 //
399
400 if (Name.Length == 0)
401 return FALSE;
402
403 //
404 // Do not allow EA name longer than 255 bytes
405 //
406 if (Name.Length > 255)
407 return FALSE;
408
409 for (Index = 0; Index < (ULONG)Name.Length; Index += 1) {
410
411 Char = Name.Buffer[Index];
412
413 //
414 // Skip over and Dbcs chacters
415 //
416 if (FsRtlIsLeadDbcsCharacter(Char)) {
417
418 ASSERT(Index != (ULONG)(Name.Length - 1));
419 Index += 1;
420 continue;
421 }
422
423 //
424 // Make sure this character is legal, and if a wild card, that
425 // wild cards are permissible.
426 //
427 if (!FsRtlIsAnsiCharacterLegalFat(Char, FALSE))
428 return FALSE;
429
430 }
431
432 return TRUE;
433 }
434
435 NTSTATUS
Ext2SetEa(IN PEXT2_IRP_CONTEXT IrpContext)436 Ext2SetEa (
437 IN PEXT2_IRP_CONTEXT IrpContext
438 )
439 {
440 PIRP Irp = NULL;
441 PIO_STACK_LOCATION IrpSp;
442
443 PDEVICE_OBJECT DeviceObject;
444
445 PEXT2_VCB Vcb = NULL;
446 PEXT2_FCB Fcb = NULL;
447 PEXT2_CCB Ccb = NULL;
448 PEXT2_MCB Mcb = NULL;
449
450 BOOLEAN MainResourceAcquired = FALSE;
451 BOOLEAN FcbLockAcquired = FALSE;
452 BOOLEAN XattrRefAcquired = FALSE;
453
454 NTSTATUS Status = STATUS_UNSUCCESSFUL;
455
456 struct ext4_xattr_ref xattr_ref;
457 PCHAR UserBuffer;
458 ULONG UserBufferLength;
459
460 PFILE_FULL_EA_INFORMATION FullEa;
461
462 _SEH2_TRY {
463
464 Ccb = IrpContext->Ccb;
465 ASSERT(Ccb != NULL);
466 ASSERT((Ccb->Identifier.Type == EXT2CCB) &&
467 (Ccb->Identifier.Size == sizeof(EXT2_CCB)));
468 DeviceObject = IrpContext->DeviceObject;
469 Vcb = (PEXT2_VCB)DeviceObject->DeviceExtension;
470 Fcb = IrpContext->Fcb;
471 Mcb = Fcb->Mcb;
472 Irp = IrpContext->Irp;
473 IrpSp = IoGetCurrentIrpStackLocation(Irp);
474
475 Irp->IoStatus.Information = 0;
476
477 //
478 // Receive input parameter from caller
479 //
480 UserBufferLength = IrpSp->Parameters.SetEa.Length;
481 UserBuffer = Irp->UserBuffer;
482
483 // Check if the EA buffer provided is valid
484 Status = IoCheckEaBufferValidity((PFILE_FULL_EA_INFORMATION)UserBuffer,
485 UserBufferLength,
486 (PULONG)&Irp->IoStatus.Information);
487 if (!NT_SUCCESS(Status))
488 _SEH2_LEAVE;
489
490 ExAcquireResourceExclusiveLite(&Vcb->FcbLock, TRUE);
491 FcbLockAcquired = TRUE;
492
493 if (!Mcb)
494 _SEH2_LEAVE;
495
496 //
497 // We do not allow multiple instance gaining EA access to the same file
498 //
499 if (!ExAcquireResourceExclusiveLite(
500 &Fcb->MainResource,
501 IsFlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT))) {
502 Status = STATUS_PENDING;
503 _SEH2_LEAVE;
504 }
505 MainResourceAcquired = TRUE;
506
507 Status = Ext2WinntError(ext4_fs_get_xattr_ref(IrpContext, Vcb, Fcb->Mcb, &xattr_ref));
508 if (!NT_SUCCESS(Status)) {
509 DbgPrint("ext4_fs_get_xattr_ref() failed!\n");
510 _SEH2_LEAVE;
511 }
512
513 XattrRefAcquired = TRUE;
514
515 //
516 // Remove all existing EA entries.
517 //
518 ext4_xattr_purge_items(&xattr_ref);
519 xattr_ref.dirty = TRUE;
520 Status = STATUS_SUCCESS;
521
522 // Iterate the whole EA buffer to do inspection
523 for (FullEa = (PFILE_FULL_EA_INFORMATION)UserBuffer;
524 FullEa < (PFILE_FULL_EA_INFORMATION)&UserBuffer[UserBufferLength];
525 FullEa = (PFILE_FULL_EA_INFORMATION)(FullEa->NextEntryOffset == 0 ?
526 &UserBuffer[UserBufferLength] :
527 (PCHAR)FullEa + FullEa->NextEntryOffset)) {
528
529 OEM_STRING EaName;
530
531 EaName.MaximumLength = EaName.Length = FullEa->EaNameLength;
532 EaName.Buffer = &FullEa->EaName[0];
533
534 // Check if EA's name is valid
535 if (!Ext2IsEaNameValid(EaName)) {
536 Irp->IoStatus.Information = (PCHAR)FullEa - UserBuffer;
537 Status = STATUS_INVALID_EA_NAME;
538 _SEH2_LEAVE;
539 }
540 }
541
542 // Now add EA entries to the inode
543 for (FullEa = (PFILE_FULL_EA_INFORMATION)UserBuffer;
544 FullEa < (PFILE_FULL_EA_INFORMATION)&UserBuffer[UserBufferLength];
545 FullEa = (PFILE_FULL_EA_INFORMATION)(FullEa->NextEntryOffset == 0 ?
546 &UserBuffer[UserBufferLength] :
547 (PCHAR)FullEa + FullEa->NextEntryOffset)) {
548
549 int ret;
550 OEM_STRING EaName;
551
552 EaName.MaximumLength = EaName.Length = FullEa->EaNameLength;
553 EaName.Buffer = &FullEa->EaName[0];
554
555 Status = Ext2WinntError(ret =
556 ext4_fs_set_xattr_ordered(&xattr_ref,
557 EXT4_XATTR_INDEX_USER,
558 EaName.Buffer,
559 EaName.Length,
560 &FullEa->EaName[0] + FullEa->EaNameLength + 1,
561 FullEa->EaValueLength));
562 if (!NT_SUCCESS(Status))
563 _SEH2_LEAVE;
564
565 }
566 } _SEH2_FINALLY {
567
568 if (XattrRefAcquired) {
569 if (!NT_SUCCESS(Status)) {
570 xattr_ref.dirty = FALSE;
571 ext4_fs_put_xattr_ref(&xattr_ref);
572 } else
573 Status = Ext2WinntError(ext4_fs_put_xattr_ref(&xattr_ref));
574 }
575
576 if (FcbLockAcquired) {
577 ExReleaseResourceLite(&Vcb->FcbLock);
578 FcbLockAcquired = FALSE;
579 }
580
581 if (MainResourceAcquired) {
582 ExReleaseResourceLite(&Fcb->MainResource);
583 }
584
585 if (NT_SUCCESS(Status)) {
586 Ext2NotifyReportChange(
587 IrpContext,
588 Vcb,
589 Mcb,
590 FILE_NOTIFY_CHANGE_EA,
591 FILE_ACTION_MODIFIED);
592 }
593
594 if (!_SEH2_AbnormalTermination()) {
595 if (Status == STATUS_PENDING || Status == STATUS_CANT_WAIT) {
596 Status = Ext2QueueRequest(IrpContext);
597 }
598 else {
599 Ext2CompleteIrpContext(IrpContext, Status);
600 }
601 }
602 } _SEH2_END;
603 return Status;
604 }
605