From 3bd1e69c2cea5e57974f02a61c48e4e33c6b2345 Mon Sep 17 00:00:00 2001 From: Daniel Rosenberg Date: Tue, 5 Mar 2024 16:20:32 -0800 Subject: [PATCH] ANDROID: fuse-bpf: Fix readdir for getdents If you call getdents with a buffer size less than a page, entries can be skipped. This correctly sets the point to continue from. Bug: 325550828 Test: getdents with low buffer size Signed-off-by: Daniel Rosenberg (cherry picked from https://android-review.googlesource.com/q/commit:506cf3f0742432c1995117f83b2528a23944989b) Merged-In: I324e7e815d31742bd4e2d70c5d07c2b09a67a7c2 Change-Id: I324e7e815d31742bd4e2d70c5d07c2b09a67a7c2 --- fs/fuse/backing.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index c4f101e9444d..68f820d5188c 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -2366,8 +2366,11 @@ static bool filldir(struct dir_context *ctx, const char *name, int namelen, return true; } -static int parse_dirfile(char *buf, size_t nbytes, struct dir_context *ctx) +static int parse_dirfile(char *buf, size_t nbytes, struct dir_context *ctx, + loff_t next_offset) { + char *buffstart = buf; + while (nbytes >= FUSE_NAME_OFFSET) { struct fuse_dirent *dirent = (struct fuse_dirent *) buf; size_t reclen = FUSE_DIRENT_SIZE(dirent); @@ -2381,12 +2384,18 @@ static int parse_dirfile(char *buf, size_t nbytes, struct dir_context *ctx) ctx->pos = dirent->off; if (!dir_emit(ctx, dirent->name, dirent->namelen, dirent->ino, - dirent->type)) - break; + dirent->type)) { + // If we can't make any progress, user buffer is too small + if (buf == buffstart) + return -EINVAL; + else + return 0; + } buf += reclen; nbytes -= reclen; } + ctx->pos = next_offset; return 0; } @@ -2433,13 +2442,12 @@ void *fuse_readdir_finalize(struct fuse_bpf_args *fa, struct file *backing_dir = ff->backing_file; int err = 0; - err = parse_dirfile(fa->out_args[1].value, fa->out_args[1].size, ctx); + err = parse_dirfile(fa->out_args[1].value, fa->out_args[1].size, ctx, fro->offset); *force_again = !!fro->again; if (*force_again && !*allow_force) err = -EINVAL; - ctx->pos = fro->offset; - backing_dir->f_pos = fro->offset; + backing_dir->f_pos = ctx->pos; free_page((unsigned long) fa->out_args[1].value); return ERR_PTR(err);