Commit db850a9b authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull UDF fix from Jan Kara:
 "A fix for a long-standing UDF bug where we were not properly
  validating directory position inside readdir"

* tag 'fs_for_v5.16-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs:
  udf: Fix crash after seekdir
parents 7cf7eed1 a48fc69f
Loading
Loading
Loading
Loading
+30 −2
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/bio.h>
#include <linux/iversion.h>

#include "udf_i.h"
#include "udf_sb.h"
@@ -43,7 +44,7 @@ static int udf_readdir(struct file *file, struct dir_context *ctx)
	struct fileIdentDesc *fi = NULL;
	struct fileIdentDesc cfi;
	udf_pblk_t block, iblock;
	loff_t nf_pos;
	loff_t nf_pos, emit_pos = 0;
	int flen;
	unsigned char *fname = NULL, *copy_name = NULL;
	unsigned char *nameptr;
@@ -57,6 +58,7 @@ static int udf_readdir(struct file *file, struct dir_context *ctx)
	int i, num, ret = 0;
	struct extent_position epos = { NULL, 0, {0, 0} };
	struct super_block *sb = dir->i_sb;
	bool pos_valid = false;

	if (ctx->pos == 0) {
		if (!dir_emit_dot(file, ctx))
@@ -67,6 +69,21 @@ static int udf_readdir(struct file *file, struct dir_context *ctx)
	if (nf_pos >= size)
		goto out;

	/*
	 * Something changed since last readdir (either lseek was called or dir
	 * changed)?  We need to verify the position correctly points at the
	 * beginning of some dir entry so that the directory parsing code does
	 * not get confused. Since UDF does not have any reliable way of
	 * identifying beginning of dir entry (names are under user control),
	 * we need to scan the directory from the beginning.
	 */
	if (!inode_eq_iversion(dir, file->f_version)) {
		emit_pos = nf_pos;
		nf_pos = 0;
	} else {
		pos_valid = true;
	}

	fname = kmalloc(UDF_NAME_LEN, GFP_NOFS);
	if (!fname) {
		ret = -ENOMEM;
@@ -122,13 +139,21 @@ static int udf_readdir(struct file *file, struct dir_context *ctx)

	while (nf_pos < size) {
		struct kernel_lb_addr tloc;
		loff_t cur_pos = nf_pos;

		/* Update file position only if we got past the current one */
		if (nf_pos >= emit_pos) {
			ctx->pos = (nf_pos >> 2) + 1;
			pos_valid = true;
		}

		fi = udf_fileident_read(dir, &nf_pos, &fibh, &cfi, &epos, &eloc,
					&elen, &offset);
		if (!fi)
			goto out;
		/* Still not at offset where user asked us to read from? */
		if (cur_pos < emit_pos)
			continue;

		liu = le16_to_cpu(cfi.lengthOfImpUse);
		lfi = cfi.lengthFileIdent;
@@ -186,8 +211,11 @@ static int udf_readdir(struct file *file, struct dir_context *ctx)
	} /* end while */

	ctx->pos = (nf_pos >> 2) + 1;
	pos_valid = true;

out:
	if (pos_valid)
		file->f_version = inode_query_iversion(dir);
	if (fibh.sbh != fibh.ebh)
		brelse(fibh.ebh);
	brelse(fibh.sbh);
+3 −0
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@
#include <linux/sched.h>
#include <linux/crc-itu-t.h>
#include <linux/exportfs.h>
#include <linux/iversion.h>

static inline int udf_match(int len1, const unsigned char *name1, int len2,
			    const unsigned char *name2)
@@ -134,6 +135,8 @@ int udf_write_fi(struct inode *inode, struct fileIdentDesc *cfi,
			mark_buffer_dirty_inode(fibh->ebh, inode);
		mark_buffer_dirty_inode(fibh->sbh, inode);
	}
	inode_inc_iversion(inode);

	return 0;
}

+2 −0
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@
#include <linux/crc-itu-t.h>
#include <linux/log2.h>
#include <asm/byteorder.h>
#include <linux/iversion.h>

#include "udf_sb.h"
#include "udf_i.h"
@@ -149,6 +150,7 @@ static struct inode *udf_alloc_inode(struct super_block *sb)
	init_rwsem(&ei->i_data_sem);
	ei->cached_extent.lstart = -1;
	spin_lock_init(&ei->i_extent_cache_lock);
	inode_set_iversion(&ei->vfs_inode, 1);

	return &ei->vfs_inode;
}