Skip to content

Commit f2060bd

Browse files
Mike SnitzerAnna Schumaker
authored andcommitted
nfs/localio: add refcounting for each iocb IO associated with NFS pgio header
Improve completion handling of as many as 3 IOs associated with each misaligned DIO by using a atomic_t to track completion of each IO. Update nfs_local_pgio_done() to use precise atomic_t accounting for remaining iov_iter (up to 3) associated with each iocb, so that each NFS LOCALIO pgio header is only released after all IOs have completed. But also allow early return if/when a short read or write occurs. Fixes reported BUG: KASAN: slab-use-after-free in nfs_local_call_read: https://lore.kernel.org/linux-nfs/[email protected]/ Reported-by: Yongcheng Yang <[email protected]> Fixes: c817248 ("nfs/localio: add proper O_DIRECT support for READ and WRITE") Signed-off-by: Mike Snitzer <[email protected]> Signed-off-by: Anna Schumaker <[email protected]>
1 parent 51a491f commit f2060bd

File tree

1 file changed

+67
-43
lines changed

1 file changed

+67
-43
lines changed

fs/nfs/localio.c

Lines changed: 67 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ struct nfs_local_kiocb {
4242
/* Begin mostly DIO-specific members */
4343
size_t end_len;
4444
short int end_iter_index;
45-
short int n_iters;
45+
atomic_t n_iters;
4646
bool iter_is_dio_aligned[NFSLOCAL_MAX_IOS];
4747
loff_t offset[NFSLOCAL_MAX_IOS] ____cacheline_aligned;
4848
struct iov_iter iters[NFSLOCAL_MAX_IOS];
@@ -407,6 +407,7 @@ nfs_local_iters_setup_dio(struct nfs_local_kiocb *iocb, int rw,
407407
iters[n_iters].count = local_dio->start_len;
408408
iocb->offset[n_iters] = iocb->hdr->args.offset;
409409
iocb->iter_is_dio_aligned[n_iters] = false;
410+
atomic_inc(&iocb->n_iters);
410411
++n_iters;
411412
}
412413

@@ -425,6 +426,7 @@ nfs_local_iters_setup_dio(struct nfs_local_kiocb *iocb, int rw,
425426
/* Save index and length of end */
426427
iocb->end_iter_index = n_iters;
427428
iocb->end_len = local_dio->end_len;
429+
atomic_inc(&iocb->n_iters);
428430
++n_iters;
429431
}
430432

@@ -448,7 +450,6 @@ nfs_local_iters_setup_dio(struct nfs_local_kiocb *iocb, int rw,
448450
}
449451
++n_iters;
450452

451-
iocb->n_iters = n_iters;
452453
return n_iters;
453454
}
454455

@@ -474,6 +475,12 @@ nfs_local_iters_init(struct nfs_local_kiocb *iocb, int rw)
474475
}
475476
len = hdr->args.count - total;
476477

478+
/*
479+
* For each iocb, iocb->n_iter is always at least 1 and we always
480+
* end io after first nfs_local_pgio_done call unless misaligned DIO.
481+
*/
482+
atomic_set(&iocb->n_iters, 1);
483+
477484
if (test_bit(NFS_IOHDR_ODIRECT, &hdr->flags)) {
478485
struct nfs_local_dio local_dio;
479486

@@ -486,7 +493,6 @@ nfs_local_iters_init(struct nfs_local_kiocb *iocb, int rw)
486493
iocb->offset[0] = hdr->args.offset;
487494
iov_iter_bvec(&iocb->iters[0], rw, iocb->bvec, v, len);
488495
iocb->iter_is_dio_aligned[0] = false;
489-
iocb->n_iters = 1;
490496
}
491497

492498
static void
@@ -506,9 +512,11 @@ nfs_local_pgio_init(struct nfs_pgio_header *hdr,
506512
hdr->task.tk_start = ktime_get();
507513
}
508514

509-
static void
510-
nfs_local_pgio_done(struct nfs_pgio_header *hdr, long status)
515+
static bool
516+
nfs_local_pgio_done(struct nfs_local_kiocb *iocb, long status, bool force)
511517
{
518+
struct nfs_pgio_header *hdr = iocb->hdr;
519+
512520
/* Must handle partial completions */
513521
if (status >= 0) {
514522
hdr->res.count += status;
@@ -519,6 +527,12 @@ nfs_local_pgio_done(struct nfs_pgio_header *hdr, long status)
519527
hdr->res.op_status = nfs_localio_errno_to_nfs4_stat(status);
520528
hdr->task.tk_status = status;
521529
}
530+
531+
if (force)
532+
return true;
533+
534+
BUG_ON(atomic_read(&iocb->n_iters) <= 0);
535+
return atomic_dec_and_test(&iocb->n_iters);
522536
}
523537

524538
static void
@@ -549,11 +563,11 @@ static inline void nfs_local_pgio_aio_complete(struct nfs_local_kiocb *iocb)
549563
queue_work(nfsiod_workqueue, &iocb->work);
550564
}
551565

552-
static void
553-
nfs_local_read_done(struct nfs_local_kiocb *iocb, long status)
566+
static void nfs_local_read_done(struct nfs_local_kiocb *iocb)
554567
{
555568
struct nfs_pgio_header *hdr = iocb->hdr;
556569
struct file *filp = iocb->kiocb.ki_filp;
570+
long status = hdr->task.tk_status;
557571

558572
if ((iocb->kiocb.ki_flags & IOCB_DIRECT) && status == -EINVAL) {
559573
/* Underlying FS will return -EINVAL if misaligned DIO is attempted. */
@@ -574,21 +588,29 @@ nfs_local_read_done(struct nfs_local_kiocb *iocb, long status)
574588
status > 0 ? status : 0, hdr->res.eof);
575589
}
576590

591+
static inline void nfs_local_read_iocb_done(struct nfs_local_kiocb *iocb)
592+
{
593+
nfs_local_read_done(iocb);
594+
nfs_local_pgio_release(iocb);
595+
}
596+
577597
static void nfs_local_read_aio_complete_work(struct work_struct *work)
578598
{
579599
struct nfs_local_kiocb *iocb =
580600
container_of(work, struct nfs_local_kiocb, work);
581601

582-
nfs_local_pgio_release(iocb);
602+
nfs_local_read_iocb_done(iocb);
583603
}
584604

585605
static void nfs_local_read_aio_complete(struct kiocb *kiocb, long ret)
586606
{
587607
struct nfs_local_kiocb *iocb =
588608
container_of(kiocb, struct nfs_local_kiocb, kiocb);
589609

590-
nfs_local_pgio_done(iocb->hdr, ret);
591-
nfs_local_read_done(iocb, ret);
610+
/* AIO completion of DIO read should always be last to complete */
611+
if (unlikely(!nfs_local_pgio_done(iocb, ret, false)))
612+
return;
613+
592614
nfs_local_pgio_aio_complete(iocb); /* Calls nfs_local_read_aio_complete_work */
593615
}
594616

@@ -599,10 +621,13 @@ static void nfs_local_call_read(struct work_struct *work)
599621
struct file *filp = iocb->kiocb.ki_filp;
600622
const struct cred *save_cred;
601623
ssize_t status;
624+
int n_iters;
602625

603626
save_cred = override_creds(filp->f_cred);
604627

605-
for (int i = 0; i < iocb->n_iters ; i++) {
628+
n_iters = atomic_read(&iocb->n_iters);
629+
for (int i = 0; i < n_iters ; i++) {
630+
/* DIO-aligned middle is always issued last with AIO completion */
606631
if (iocb->iter_is_dio_aligned[i]) {
607632
iocb->kiocb.ki_flags |= IOCB_DIRECT;
608633
iocb->kiocb.ki_complete = nfs_local_read_aio_complete;
@@ -612,18 +637,14 @@ static void nfs_local_call_read(struct work_struct *work)
612637
iocb->kiocb.ki_pos = iocb->offset[i];
613638
status = filp->f_op->read_iter(&iocb->kiocb, &iocb->iters[i]);
614639
if (status != -EIOCBQUEUED) {
615-
nfs_local_pgio_done(iocb->hdr, status);
616-
if (iocb->hdr->task.tk_status)
640+
if (nfs_local_pgio_done(iocb, status, false)) {
641+
nfs_local_read_iocb_done(iocb);
617642
break;
643+
}
618644
}
619645
}
620646

621647
revert_creds(save_cred);
622-
623-
if (status != -EIOCBQUEUED) {
624-
nfs_local_read_done(iocb, status);
625-
nfs_local_pgio_release(iocb);
626-
}
627648
}
628649

629650
static int
@@ -738,11 +759,10 @@ static void nfs_local_vfs_getattr(struct nfs_local_kiocb *iocb)
738759
fattr->du.nfs3.used = stat.blocks << 9;
739760
}
740761

741-
static void
742-
nfs_local_write_done(struct nfs_local_kiocb *iocb, long status)
762+
static void nfs_local_write_done(struct nfs_local_kiocb *iocb)
743763
{
744764
struct nfs_pgio_header *hdr = iocb->hdr;
745-
struct inode *inode = hdr->inode;
765+
long status = hdr->task.tk_status;
746766

747767
dprintk("%s: wrote %ld bytes.\n", __func__, status > 0 ? status : 0);
748768

@@ -761,28 +781,36 @@ nfs_local_write_done(struct nfs_local_kiocb *iocb, long status)
761781
nfs_set_pgio_error(hdr, -ENOSPC, hdr->args.offset);
762782
status = -ENOSPC;
763783
/* record -ENOSPC in terms of nfs_local_pgio_done */
764-
nfs_local_pgio_done(hdr, status);
784+
(void) nfs_local_pgio_done(iocb, status, true);
765785
}
766786
if (hdr->task.tk_status < 0)
767-
nfs_reset_boot_verifier(inode);
787+
nfs_reset_boot_verifier(hdr->inode);
788+
}
789+
790+
static inline void nfs_local_write_iocb_done(struct nfs_local_kiocb *iocb)
791+
{
792+
nfs_local_write_done(iocb);
793+
nfs_local_vfs_getattr(iocb);
794+
nfs_local_pgio_release(iocb);
768795
}
769796

770797
static void nfs_local_write_aio_complete_work(struct work_struct *work)
771798
{
772799
struct nfs_local_kiocb *iocb =
773800
container_of(work, struct nfs_local_kiocb, work);
774801

775-
nfs_local_vfs_getattr(iocb);
776-
nfs_local_pgio_release(iocb);
802+
nfs_local_write_iocb_done(iocb);
777803
}
778804

779805
static void nfs_local_write_aio_complete(struct kiocb *kiocb, long ret)
780806
{
781807
struct nfs_local_kiocb *iocb =
782808
container_of(kiocb, struct nfs_local_kiocb, kiocb);
783809

784-
nfs_local_pgio_done(iocb->hdr, ret);
785-
nfs_local_write_done(iocb, ret);
810+
/* AIO completion of DIO write should always be last to complete */
811+
if (unlikely(!nfs_local_pgio_done(iocb, ret, false)))
812+
return;
813+
786814
nfs_local_pgio_aio_complete(iocb); /* Calls nfs_local_write_aio_complete_work */
787815
}
788816

@@ -793,13 +821,17 @@ static void nfs_local_call_write(struct work_struct *work)
793821
struct file *filp = iocb->kiocb.ki_filp;
794822
unsigned long old_flags = current->flags;
795823
const struct cred *save_cred;
824+
bool force_done = false;
796825
ssize_t status;
826+
int n_iters;
797827

798828
current->flags |= PF_LOCAL_THROTTLE | PF_MEMALLOC_NOIO;
799829
save_cred = override_creds(filp->f_cred);
800830

801831
file_start_write(filp);
802-
for (int i = 0; i < iocb->n_iters ; i++) {
832+
n_iters = atomic_read(&iocb->n_iters);
833+
for (int i = 0; i < n_iters ; i++) {
834+
/* DIO-aligned middle is always issued last with AIO completion */
803835
if (iocb->iter_is_dio_aligned[i]) {
804836
iocb->kiocb.ki_flags |= IOCB_DIRECT;
805837
iocb->kiocb.ki_complete = nfs_local_write_aio_complete;
@@ -812,35 +844,27 @@ static void nfs_local_call_write(struct work_struct *work)
812844
if (unlikely(status >= 0 && status < iocb->iters[i].count)) {
813845
/* partial write */
814846
if (i == iocb->end_iter_index) {
815-
/* Must not account partial end, otherwise, due
816-
* to end being issued before middle: the partial
847+
/* Must not account DIO partial end, otherwise (due
848+
* to end being issued before middle): the partial
817849
* write accounting in nfs_local_write_done()
818850
* would incorrectly advance hdr->args.offset
819851
*/
820852
status = 0;
821853
} else {
822-
/* Partial write at start or buffered middle,
823-
* exit early.
824-
*/
825-
nfs_local_pgio_done(iocb->hdr, status);
826-
break;
854+
/* Partial write at start or middle, force done */
855+
force_done = true;
827856
}
828857
}
829-
nfs_local_pgio_done(iocb->hdr, status);
830-
if (iocb->hdr->task.tk_status)
858+
if (nfs_local_pgio_done(iocb, status, force_done)) {
859+
nfs_local_write_iocb_done(iocb);
831860
break;
861+
}
832862
}
833863
}
834864
file_end_write(filp);
835865

836866
revert_creds(save_cred);
837867
current->flags = old_flags;
838-
839-
if (status != -EIOCBQUEUED) {
840-
nfs_local_write_done(iocb, status);
841-
nfs_local_vfs_getattr(iocb);
842-
nfs_local_pgio_release(iocb);
843-
}
844868
}
845869

846870
static int

0 commit comments

Comments
 (0)