[Devel] [PATCH VZ9 1/2] fs/fuse kio: get refcnt on pages for zero-copy read via krpc.

Liu Kui kui.liu at virtuozzo.com
Mon Dec 9 11:46:54 MSK 2024


Pages passed to krpc for zero-copy read could be released before the
krpc request completes, for example in case of aborting a file that has
ongoing read request issued by the page cache, pages used by these read
requests could be accessed later by krpc after they are released.
So to avoid such use-after-free issue, krpc need to get extra refcnt on
these pages.

Related to #VSTOR-95997
Signed-off-by: Liu Kui <kui.liu at virtuozzo.com>
---
 fs/fuse/kio/pcs/pcs_krpc.c | 68 ++++++++++++++++++++++++--------------
 fs/fuse/kio/pcs/pcs_krpc.h |  2 ++
 2 files changed, 45 insertions(+), 25 deletions(-)

diff --git a/fs/fuse/kio/pcs/pcs_krpc.c b/fs/fuse/kio/pcs/pcs_krpc.c
index 9af1c93b20ed..ab2534f7a952 100644
--- a/fs/fuse/kio/pcs/pcs_krpc.c
+++ b/fs/fuse/kio/pcs/pcs_krpc.c
@@ -17,6 +17,8 @@
 #include "pcs_req.h"
 #include "pcs_krpc.h"
 
+static void kreq_release_data_chunks(struct krpc_req *kreq);
+
 extern unsigned int pcs_krpc_version;
 
 struct kmem_cache *krpc_req_cachep;
@@ -69,27 +71,14 @@ static void krpc_req_complete(struct krpc_req *kreq, int error)
 {
 	struct krpc_completion *comp = &kreq->completion;
 	struct pcs_krpc *krpc = kreq->krpc;
-	int i;
 
 	BUG_ON(!comp->xid);
 
 	comp->result = error;
 
+	kreq_release_data_chunks(kreq);
 	pcs_mr_put(kreq->hdr_chunk.mr);
 
-	for (i = 0; i < kreq->nr_data_chunks; i++) {
-		struct krpc_chunk *chunk;
-
-		chunk = &kreq->data_chunks[i];
-		if (chunk->type == KRPC_CHUNK_TYPE_UMEM)
-			pcs_umem_release(chunk->umem);
-		else if (chunk->type == KRPC_CHUNK_TYPE_MR)
-			pcs_mr_put(chunk->mr);
-	}
-
-	if (kreq->data_chunks != &kreq->inline_data_chunks[0])
-		kfree(kreq->data_chunks);
-
 	spin_lock(&krpc->lock);
 	list_del(&kreq->link);
 
@@ -309,6 +298,39 @@ static int pcs_krpc_ioctl_recv_msg(struct pcs_krpc *krpc, struct pcs_krpc_ioc_re
 	return res;
 }
 
+static void kreq_release_data_chunks(struct krpc_req *kreq)
+{
+	struct krpc_chunk *chunk;
+	int i;
+
+	for (i = 0; i < kreq->nr_data_chunks; i++) {
+		chunk = &kreq->data_chunks[i];
+
+		switch (chunk->type) {
+		case KRPC_CHUNK_TYPE_UMEM:
+			pcs_umem_release(chunk->umem);
+			break;
+		case KRPC_CHUNK_TYPE_MR:
+			pcs_mr_put(chunk->mr);
+			break;
+		case KRPC_CHUNK_TYPE_ZC:
+			int j, end;
+
+			end = chunk->bvec_idx_start + chunk->nr_bvecs;
+			for (j = chunk->bvec_idx_start; j < end; j++)
+				put_page(kreq->data_bvecs[j].bv_page);
+
+			break;
+		default:
+			WARN_ON("Invalid chunk type");
+			break;
+		}
+	}
+
+	if (kreq->data_chunks != &kreq->inline_data_chunks[0])
+		kfree(kreq->data_chunks);
+}
+
 
 static void kreq_prepare_data_buff(struct krpc_req *kreq, struct krpc_chunk *chunk)
 {
@@ -334,6 +356,8 @@ static void kreq_prepare_data_buff(struct krpc_req *kreq, struct krpc_chunk *chu
 			i++;
 		}
 
+		chunk->bvec_idx_start = kreq->nr_data_bvecs;
+		chunk->nr_bvecs = 0;
 		offset = pos;
 		while (len) {
 			/* data bvec array overflow? */
@@ -341,13 +365,15 @@ static void kreq_prepare_data_buff(struct krpc_req *kreq, struct krpc_chunk *chu
 
 			bvec = &kreq->data_bvecs[kreq->nr_data_bvecs];
 
-			bvec->bv_page = ia->ap.pages[i];
 			bvec->bv_offset = ia->ap.descs[i].offset + offset;
 			bvec->bv_len = len < (ia->ap.descs[i].length - offset) ? len :
 					(ia->ap.descs[i].length - offset);
+			bvec->bv_page = ia->ap.pages[i];
+			get_page(bvec->bv_page);
 
 			len -= bvec->bv_len;
 			kreq->nr_data_bvecs++;
+			chunk->nr_bvecs++;
 			i++;
 			offset = 0;
 		}
@@ -458,6 +484,7 @@ static int pcs_krpc_ioctl_send_msg(struct pcs_krpc *krpc, struct pcs_krpc_ioc_se
 		case PCS_KRPC_BUF_TYPE_ZC: {
 			struct pcs_krpc_buf_desc_zc *chunk_bdzc = (struct pcs_krpc_buf_desc_zc *)chunk_bd;
 
+			WARN_ON(iocmsg->nr_data_chunks != 1);
 			chunk->type = KRPC_CHUNK_TYPE_ZC;
 			chunk->addr = chunk_bdzc->offset;
 			chunk->req = fuse_dev_find_request(chunk_bdzc->devfd, chunk_bdzc->unique);
@@ -509,18 +536,9 @@ static int pcs_krpc_ioctl_send_msg(struct pcs_krpc *krpc, struct pcs_krpc_ioc_se
 	return 0;
 
 err_free_data_chunk:
-	for (i = 0; i < kreq->nr_data_chunks; i++) {
-		chunk = &kreq->data_chunks[i];
-		if (chunk->type == KRPC_CHUNK_TYPE_UMEM)
-			pcs_umem_release(chunk->umem);
-		else if (chunk->type == KRPC_CHUNK_TYPE_MR)
-			pcs_mr_put(chunk->mr);
-	}
+	kreq_release_data_chunks(kreq);
 	pcs_mr_put(kreq->hdr_chunk.mr);
 
-	if (kreq->data_chunks != &kreq->inline_data_chunks[0])
-		kfree(kreq->data_chunks);
-
 err_free_kreq:
 	krpc_req_free(kreq);
 	return res;
diff --git a/fs/fuse/kio/pcs/pcs_krpc.h b/fs/fuse/kio/pcs/pcs_krpc.h
index 23b170c00d7a..556021b1d1f5 100644
--- a/fs/fuse/kio/pcs/pcs_krpc.h
+++ b/fs/fuse/kio/pcs/pcs_krpc.h
@@ -25,6 +25,8 @@ struct krpc_chunk {
 		struct pcs_umem *umem;
 		struct fuse_req *req;
 	};
+	u16	 bvec_idx_start;
+	u16	 nr_bvecs;
 };
 
 #define PCS_KRPC_HASH_SIZE	1024
-- 
2.39.5 (Apple Git-154)



More information about the Devel mailing list