From: Greg Kroah-Hartman Date: Mon, 22 Nov 2021 13:09:30 +0000 (+0100) Subject: 5.4-stable patches X-Git-Tag: v5.15.5~54 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=f7d64273df48fb9251aec99a66bcd75e5e26dfa4;p=thirdparty%2Fkernel%2Fstable-queue.git 5.4-stable patches added patches: udf-fix-crash-after-seekdir.patch --- diff --git a/queue-5.4/series b/queue-5.4/series index 67558cec1ae..9ae29a55503 100644 --- a/queue-5.4/series +++ b/queue-5.4/series @@ -80,3 +80,4 @@ ipc-warn-if-trying-to-remove-ipc-object-which-is-absent.patch mm-kmemleak-slob-respect-slab_noleaktrace-flag.patch x86-hyperv-fix-null-deref-in-set_hv_tscchange_cb-if-hyper-v-setup-fails.patch s390-kexec-fix-memory-leak-of-ipl-report-buffer.patch +udf-fix-crash-after-seekdir.patch diff --git a/queue-5.4/udf-fix-crash-after-seekdir.patch b/queue-5.4/udf-fix-crash-after-seekdir.patch new file mode 100644 index 00000000000..73e626394d6 --- /dev/null +++ b/queue-5.4/udf-fix-crash-after-seekdir.patch @@ -0,0 +1,157 @@ +From a48fc69fe6588b48d878d69de223b91a386a7cb4 Mon Sep 17 00:00:00 2001 +From: Jan Kara +Date: Thu, 4 Nov 2021 15:22:35 +0100 +Subject: udf: Fix crash after seekdir + +From: Jan Kara + +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 +CC: stable@vger.kernel.org +Signed-off-by: Jan Kara +Signed-off-by: Greg Kroah-Hartman +--- + 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 + #include + #include ++#include + + #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 + #include + #include ++#include + + 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 + #include + #include ++#include + + #include "udf_sb.h" + #include "udf_i.h" +@@ -149,6 +150,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; + }