--- /dev/null
+From a48fc69fe6588b48d878d69de223b91a386a7cb4 Mon Sep 17 00:00:00 2001
+From: Jan Kara <jack@suse.cz>
+Date: Thu, 4 Nov 2021 15:22:35 +0100
+Subject: udf: Fix crash after seekdir
+
+From: Jan Kara <jack@suse.cz>
+
+commit a48fc69fe6588b48d878d69de223b91a386a7cb4 upstream.
+
+udf_readdir() didn't validate the directory position it should start
+reading from. Thus when user uses lseek(2) on directory file descriptor
+it can trick udf_readdir() into reading from a position in the middle of
+directory entry which then upsets directory parsing code resulting in
+errors or even possible kernel crashes. Similarly when the directory is
+modified between two readdir calls, the directory position need not be
+valid anymore.
+
+Add code to validate current offset in the directory. This is actually
+rather expensive for UDF as we need to read from the beginning of the
+directory and parse all directory entries. This is because in UDF a
+directory is just a stream of data containing directory entries and
+since file names are fully under user's control we cannot depend on
+detecting magic numbers and checksums in the header of directory entry
+as a malicious attacker could fake them. We skip this step if we detect
+that nothing changed since the last readdir call.
+
+Reported-by: Nathan Wilson <nate@chickenbrittle.com>
+CC: stable@vger.kernel.org
+Signed-off-by: Jan Kara <jack@suse.cz>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/udf/dir.c | 32 ++++++++++++++++++++++++++++++--
+ fs/udf/namei.c | 3 +++
+ fs/udf/super.c | 2 ++
+ 3 files changed, 35 insertions(+), 2 deletions(-)
+
+--- a/fs/udf/dir.c
++++ b/fs/udf/dir.c
+@@ -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"
+@@ -44,7 +45,7 @@ static int udf_readdir(struct file *file
+ 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;
+@@ -58,6 +59,7 @@ static int udf_readdir(struct file *file
+ 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))
+@@ -68,6 +70,21 @@ static int udf_readdir(struct file *file
+ 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;
+@@ -123,13 +140,21 @@ static int udf_readdir(struct file *file
+
+ while (nf_pos < size) {
+ struct kernel_lb_addr tloc;
++ loff_t cur_pos = nf_pos;
+
+- ctx->pos = (nf_pos >> 2) + 1;
++ /* 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;
+@@ -187,8 +212,11 @@ static int udf_readdir(struct file *file
+ } /* 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);
+--- a/fs/udf/namei.c
++++ b/fs/udf/namei.c
+@@ -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)
+@@ -135,6 +136,8 @@ int udf_write_fi(struct inode *inode, st
+ mark_buffer_dirty_inode(fibh->ebh, inode);
+ mark_buffer_dirty_inode(fibh->sbh, inode);
+ }
++ inode_inc_iversion(inode);
++
+ return 0;
+ }
+
+--- a/fs/udf/super.c
++++ b/fs/udf/super.c
+@@ -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"
+@@ -151,6 +152,7 @@ static struct inode *udf_alloc_inode(str
+ 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;
+ }