Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 12495ea3 authored by Krzysztof Błaszkowski's avatar Krzysztof Błaszkowski Committed by Christoph Hellwig
Browse files

freevxfs: refactor readdir and lookup code



This change fixes also a buffer overflow which was caused by
accessing address space beyond mapped page

Signed-off-by: default avatarKrzysztof Błaszkowski <kb@sysmikro.com.pl>
Signed-off-by: default avatarChristoph Hellwig <hch@lst.de>
parent f2fe2fa1
Loading
Loading
Loading
Loading
+108 −125
Original line number Diff line number Diff line
@@ -62,35 +62,6 @@ const struct file_operations vxfs_dir_operations = {
	.iterate_shared =	vxfs_readdir,
};

static inline u_long
dir_blocks(struct inode *ip)
{
	u_long			bsize = ip->i_sb->s_blocksize;
	return (ip->i_size + bsize - 1) & ~(bsize - 1);
}

/*
 * NOTE! unlike strncmp, vxfs_match returns 1 for success, 0 for failure.
 *
 * len <= VXFS_NAMELEN and de != NULL are guaranteed by caller.
 */
static inline int
vxfs_match(struct vxfs_sb_info *sbi, int len, const char *const name,
		struct vxfs_direct *de)
{
	if (len != fs16_to_cpu(sbi, de->d_namelen))
		return 0;
	if (!de->d_ino)
		return 0;
	return !memcmp(name, de->d_name, len);
}

static inline struct vxfs_direct *
vxfs_next_entry(struct vxfs_sb_info *sbi, struct vxfs_direct *de)
{
	return ((struct vxfs_direct *)
		((char *)de + fs16_to_cpu(sbi, de->d_reclen)));
}

/**
 * vxfs_find_entry - find a mathing directory entry for a dentry
@@ -109,53 +80,64 @@ vxfs_next_entry(struct vxfs_sb_info *sbi, struct vxfs_direct *de)
static struct vxfs_direct *
vxfs_find_entry(struct inode *ip, struct dentry *dp, struct page **ppp)
{
	struct vxfs_sb_info		*sbi = VXFS_SBI(ip->i_sb);
	u_long				npages, page, nblocks, pblocks, block;
	u_long bsize = ip->i_sb->s_blocksize;
	const char *name = dp->d_name.name;
	int namelen = dp->d_name.len;
	loff_t limit = VXFS_DIRROUND(ip->i_size);
	struct vxfs_direct *de_exit = NULL;
	loff_t pos = 0;
	struct vxfs_sb_info *sbi = VXFS_SBI(ip->i_sb);

	npages = dir_pages(ip);
	nblocks = dir_blocks(ip);
	pblocks = VXFS_BLOCK_PER_PAGE(ip->i_sb);
	
	for (page = 0; page < npages; page++) {
		caddr_t			kaddr;
	while (pos < limit) {
		struct page *pp;
		char *kaddr;
		int pg_ofs = pos & ~PAGE_MASK;

		pp = vxfs_get_page(ip->i_mapping, page);
		pp = vxfs_get_page(ip->i_mapping, pos >> PAGE_SHIFT);
		if (IS_ERR(pp))
			continue;
		kaddr = (caddr_t)page_address(pp);
			return NULL;
		kaddr = (char *)page_address(pp);

		for (block = 0; block <= nblocks && block <= pblocks; block++) {
			caddr_t			baddr, limit;
			struct vxfs_dirblk	*dbp;
		while (pg_ofs < PAGE_SIZE && pos < limit) {
			struct vxfs_direct *de;

			baddr = kaddr + (block * bsize);
			limit = baddr + bsize - VXFS_DIRLEN(1);
			if ((pos & (bsize - 1)) < 4) {
				struct vxfs_dirblk *dbp =
					(struct vxfs_dirblk *)
					 (kaddr + (pos & ~PAGE_MASK));
				int overhead = VXFS_DIRBLKOV(sbi, dbp);

			dbp = (struct vxfs_dirblk *)baddr;
			de = (struct vxfs_direct *)
				(baddr + VXFS_DIRBLKOV(sbi, dbp));
				pos += overhead;
				pg_ofs += overhead;
			}
			de = (struct vxfs_direct *)(kaddr + pg_ofs);

			for (; (caddr_t)de <= limit;
					de = vxfs_next_entry(sbi, de)) {
				if (!de->d_reclen)
			if (!de->d_reclen) {
				pos += bsize - 1;
				pos &= ~(bsize - 1);
				break;
			}

			pg_ofs += fs16_to_cpu(sbi, de->d_reclen);
			pos += fs16_to_cpu(sbi, de->d_reclen);
			if (!de->d_ino)
				continue;
				if (vxfs_match(sbi, namelen, name, de)) {

			if (namelen != fs16_to_cpu(sbi, de->d_namelen))
				continue;
			if (!memcmp(name, de->d_name, namelen)) {
				*ppp = pp;
					return (de);
				}
				de_exit = de;
				break;
			}
		}
		if (!de_exit)
			vxfs_put_page(pp);
		else
			break;
	}

	return NULL;
	return de_exit;
}

/**
@@ -238,80 +220,81 @@ vxfs_readdir(struct file *fp, struct dir_context *ctx)
{
	struct inode		*ip = file_inode(fp);
	struct super_block	*sbp = ip->i_sb;
	struct vxfs_sb_info	*sbi = VXFS_SBI(sbp);
	u_long			bsize = sbp->s_blocksize;
	u_long			page, npages, block, pblocks, nblocks, offset;
	loff_t			pos;

	loff_t			pos, limit;
	struct vxfs_sb_info	*sbi = VXFS_SBI(sbp);

	if (ctx->pos == 0) {
		if (!dir_emit_dot(fp, ctx))
			return 0;
		ctx->pos = 1;
			goto out;
		ctx->pos++;
	}
	if (ctx->pos == 1) {
		if (!dir_emit(ctx, "..", 2, VXFS_INO(ip)->vii_dotdot, DT_DIR))
			return 0;
		ctx->pos = 2;
			goto out;
		ctx->pos++;
	}
	pos = ctx->pos - 2;

	if (pos > VXFS_DIRROUND(ip->i_size))
		return 0;

	npages = dir_pages(ip);
	nblocks = dir_blocks(ip);
	pblocks = VXFS_BLOCK_PER_PAGE(sbp);
	limit = VXFS_DIRROUND(ip->i_size);
	if (ctx->pos > limit)
		goto out;

	page = pos >> PAGE_SHIFT;
	offset = pos & ~PAGE_MASK;
	block = (u_long)(pos >> sbp->s_blocksize_bits) % pblocks;
	pos = ctx->pos & ~3L;

	for (; page < npages; page++, block = 0) {
		char			*kaddr;
	while (pos < limit) {
		struct page *pp;
		char *kaddr;
		int pg_ofs = pos & ~PAGE_MASK;
		int rc = 0;

		pp = vxfs_get_page(ip->i_mapping, page);
		pp = vxfs_get_page(ip->i_mapping, pos >> PAGE_SHIFT);
		if (IS_ERR(pp))
			continue;
			return -ENOMEM;

		kaddr = (char *)page_address(pp);

		for (; block <= nblocks && block <= pblocks; block++) {
			char			*baddr, *limit;
			struct vxfs_dirblk	*dbp;
		while (pg_ofs < PAGE_SIZE && pos < limit) {
			struct vxfs_direct *de;

			baddr = kaddr + (block * bsize);
			limit = baddr + bsize - VXFS_DIRLEN(1);
			if ((pos & (bsize - 1)) < 4) {
				struct vxfs_dirblk *dbp =
					(struct vxfs_dirblk *)
					 (kaddr + (pos & ~PAGE_MASK));
				int overhead = VXFS_DIRBLKOV(sbi, dbp);

			dbp = (struct vxfs_dirblk *)baddr;
			de = (struct vxfs_direct *)
				(offset ?
				 (kaddr + offset) :
				 (baddr + VXFS_DIRBLKOV(sbi, dbp)));
				pos += overhead;
				pg_ofs += overhead;
			}
			de = (struct vxfs_direct *)(kaddr + pg_ofs);

			for (; (char *)de <= limit;
					de = vxfs_next_entry(sbi, de)) {
				if (!de->d_reclen)
			if (!de->d_reclen) {
				pos += bsize - 1;
				pos &= ~(bsize - 1);
				break;
			}

			pg_ofs += fs16_to_cpu(sbi, de->d_reclen);
			pos += fs16_to_cpu(sbi, de->d_reclen);
			if (!de->d_ino)
				continue;

				offset = (char *)de - kaddr;
				ctx->pos = ((page << PAGE_SHIFT) | offset) + 2;
				if (!dir_emit(ctx, de->d_name,
			rc = dir_emit(ctx, de->d_name,
					fs16_to_cpu(sbi, de->d_namelen),
					fs32_to_cpu(sbi, de->d_ino),
						DT_UNKNOWN)) {
					vxfs_put_page(pp);
					return 0;
				}
					DT_UNKNOWN);
			if (!rc) {
				/* the dir entry was not read, fix pos. */
				pos -= fs16_to_cpu(sbi, de->d_reclen);
				break;
			}
			offset = 0;
		}
		vxfs_put_page(pp);
		offset = 0;
		if (!rc)
			break;
	}
	ctx->pos = ((page << PAGE_SHIFT) | offset) + 2;

	ctx->pos = pos | 2;

out:
	return 0;
}