From: Greg Kroah-Hartman Date: Tue, 12 Mar 2019 12:57:46 +0000 (-0700) Subject: 5.0-stable patches X-Git-Tag: v5.0.2~12 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=eb2402342b0a521441fe708be6b1ae0667c386a9;p=thirdparty%2Fkernel%2Fstable-queue.git 5.0-stable patches added patches: ath9k-avoid-of-no-eeprom-quirks-without-qca-no-eeprom.patch gfs2-fix-missed-wakeups-in-find_insert_glock.patch scripts-gdb-replace-flags-ms_xyz-sb_xyz.patch staging-erofs-compressed_pages-should-not-be-accessed-again-after-freed.patch staging-erofs-keep-corrupted-fs-from-crashing-kernel-in-erofs_namei.patch --- diff --git a/queue-5.0/ath9k-avoid-of-no-eeprom-quirks-without-qca-no-eeprom.patch b/queue-5.0/ath9k-avoid-of-no-eeprom-quirks-without-qca-no-eeprom.patch new file mode 100644 index 00000000000..88a834cbf3f --- /dev/null +++ b/queue-5.0/ath9k-avoid-of-no-eeprom-quirks-without-qca-no-eeprom.patch @@ -0,0 +1,88 @@ +From ce938231bd3b1d7af3cbd8836f084801090470e1 Mon Sep 17 00:00:00 2001 +From: "Daniel F. Dickinson" +Date: Sat, 22 Dec 2018 01:09:13 -0500 +Subject: ath9k: Avoid OF no-EEPROM quirks without qca,no-eeprom + +From: Daniel F. Dickinson + +commit ce938231bd3b1d7af3cbd8836f084801090470e1 upstream. + +ath9k_of_init() function[0] was initially written on the assumption that +if someone had an explicit ath9k OF node that "there must be something +wrong, why would someone add an OF node if everything is fine"[1] +(Quoting Martin Blumenstingl ) + +"it turns out it's not that simple. with your requirements I'm now aware +of two use-cases where the current code in ath9k_of_init() doesn't work +without modifications"[1] + +The "your requirements" Martin speaks of is the result of the fact that I +have a device (PowerCloud Systems CR5000) has some kind of default - not +unique mac address - set and requires to set the correct MAC address via +mac-address devicetree property, however: + +"some cards come with a physical EEPROM chip [or OTP] so "qca,no-eeprom" +should not be set (your use-case). in this case AH_USE_EEPROM should be +set (which is the default when there is no OF node)"[1] + +The other use case is: + +the firmware on some PowerMac G5 seems to add a OF node for the ath9k +card automatically. depending on the EEPROM on the card AH_NO_EEP_SWAP +should be unset (which is the default when there is no OF node). see [3] + +After this patch to ath9k_of_init() the new behavior will be: + + if there's no OF node then everything is the same as before + if there's an empty OF node then ath9k will use the hardware EEPROM + (before ath9k would fail to initialize because no EEPROM data was + provided by userspace) + if there's an OF node with only a MAC address then ath9k will use + the MAC address and the hardware EEPROM (see the case above) + with "qca,no-eeprom" EEPROM data from userspace will be requested. + the behavior here will not change +[1] + +Martin provides additional background on EEPROM swapping[1]. + +Thanks to Christian Lamparter for all his help on +troubleshooting this issue and the basis for this patch. + +[0]https://elixir.bootlin.com/linux/v4.20-rc7/source/drivers/net/wireless/ath/ath9k/init.c#L615 +[1]https://github.com/openwrt/openwrt/pull/1645#issuecomment-448027058 +[2]https://github.com/openwrt/openwrt/pull/1613 +[3]https://patchwork.kernel.org/patch/10241731/ + +Fixes: 138b41253d9c ("ath9k: parse the device configuration from an OF node") +Reviewed-by: Martin Blumenstingl +Tested-by: Martin Blumenstingl +Signed-off-by: Daniel F. Dickinson +Signed-off-by: Kalle Valo +Cc: Christian Lamparter +Signed-off-by: Greg Kroah-Hartman + +--- + drivers/net/wireless/ath/ath9k/init.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +--- a/drivers/net/wireless/ath/ath9k/init.c ++++ b/drivers/net/wireless/ath/ath9k/init.c +@@ -636,15 +636,15 @@ static int ath9k_of_init(struct ath_soft + ret = ath9k_eeprom_request(sc, eeprom_name); + if (ret) + return ret; ++ ++ ah->ah_flags &= ~AH_USE_EEPROM; ++ ah->ah_flags |= AH_NO_EEP_SWAP; + } + + mac = of_get_mac_address(np); + if (mac) + ether_addr_copy(common->macaddr, mac); + +- ah->ah_flags &= ~AH_USE_EEPROM; +- ah->ah_flags |= AH_NO_EEP_SWAP; +- + return 0; + } + diff --git a/queue-5.0/gfs2-fix-missed-wakeups-in-find_insert_glock.patch b/queue-5.0/gfs2-fix-missed-wakeups-in-find_insert_glock.patch new file mode 100644 index 00000000000..29404caa7fe --- /dev/null +++ b/queue-5.0/gfs2-fix-missed-wakeups-in-find_insert_glock.patch @@ -0,0 +1,38 @@ +From 605b0487f0bc1ae9963bf52ece0f5c8055186f81 Mon Sep 17 00:00:00 2001 +From: Andreas Gruenbacher +Date: Wed, 6 Mar 2019 15:41:57 +0100 +Subject: gfs2: Fix missed wakeups in find_insert_glock + +From: Andreas Gruenbacher + +commit 605b0487f0bc1ae9963bf52ece0f5c8055186f81 upstream. + +Mark Syms has reported seeing tasks that are stuck waiting in +find_insert_glock. It turns out that struct lm_lockname contains four padding +bytes on 64-bit architectures that function glock_waitqueue doesn't skip when +hashing the glock name. As a result, we can end up waking up the wrong +waitqueue, and the waiting tasks may be stuck forever. + +Fix that by using ht_parms.key_len instead of sizeof(struct lm_lockname) for +the key length. + +Reported-by: Mark Syms +Signed-off-by: Andreas Gruenbacher +Signed-off-by: Bob Peterson +Signed-off-by: Greg Kroah-Hartman + +--- + fs/gfs2/glock.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/fs/gfs2/glock.c ++++ b/fs/gfs2/glock.c +@@ -107,7 +107,7 @@ static int glock_wake_function(wait_queu + + static wait_queue_head_t *glock_waitqueue(struct lm_lockname *name) + { +- u32 hash = jhash2((u32 *)name, sizeof(*name) / 4, 0); ++ u32 hash = jhash2((u32 *)name, ht_parms.key_len / 4, 0); + + return glock_wait_table + hash_32(hash, GLOCK_WAIT_TABLE_BITS); + } diff --git a/queue-5.0/scripts-gdb-replace-flags-ms_xyz-sb_xyz.patch b/queue-5.0/scripts-gdb-replace-flags-ms_xyz-sb_xyz.patch new file mode 100644 index 00000000000..0086e60bd09 --- /dev/null +++ b/queue-5.0/scripts-gdb-replace-flags-ms_xyz-sb_xyz.patch @@ -0,0 +1,95 @@ +From 663cb6340c6e84fe29aa6d0fa63d85ea6bd6cd19 Mon Sep 17 00:00:00 2001 +From: Jackie Liu +Date: Thu, 7 Mar 2019 16:30:10 -0800 +Subject: scripts/gdb: replace flags (MS_xyz -> SB_xyz) + +From: Jackie Liu + +commit 663cb6340c6e84fe29aa6d0fa63d85ea6bd6cd19 upstream. + +Since commit 1751e8a6cb93 ("Rename superblock flags (MS_xyz -> +SB_xyz)"), scripts/gdb should be updated to replace MS_xyz with SB_xyz. + +This change didn't directly affect the running operation of scripts/gdb +until commit e262e32d6bde "vfs: Suppress MS_* flag defs within the +kernel unless explicitly enabled" removed the definitions used by +constants.py. + +Update constants.py.in to utilise the new internal flags, matching the +implementation at fs/proc_namespace.c::show_sb_opts. + +Note to stable, e262e32d6bde landed in v5.0-rc1 (which was just +released), so we'll want this picked back to 5.0 stable once this patch +hits mainline (akpm just picked it up). Without this, debugging a +kernel a kernel via GDB+QEMU is broken in the 5.0 release. + +[kieran.bingham@ideasonboard.com: add fixes tag, reword commit message] +Link: http://lkml.kernel.org/r/20190305103014.25847-1-kieran.bingham@ideasonboard.com +Fixes: e262e32d6bde "vfs: Suppress MS_* flag defs within the kernel unless explicitly enabled" +Signed-off-by: Jackie Liu +Signed-off-by: Kieran Bingham +Tested-by: Nick Desaulniers +Tested-by: Kieran Bingham +Cc: Felipe Balbi +Cc: Dan Robertson +Cc: Jan Kiszka +Cc: David Howells +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Linus Torvalds +Signed-off-by: Greg Kroah-Hartman + +--- + scripts/gdb/linux/constants.py.in | 12 ++++++------ + scripts/gdb/linux/proc.py | 12 ++++++------ + 2 files changed, 12 insertions(+), 12 deletions(-) + +--- a/scripts/gdb/linux/constants.py.in ++++ b/scripts/gdb/linux/constants.py.in +@@ -37,12 +37,12 @@ + import gdb + + /* linux/fs.h */ +-LX_VALUE(MS_RDONLY) +-LX_VALUE(MS_SYNCHRONOUS) +-LX_VALUE(MS_MANDLOCK) +-LX_VALUE(MS_DIRSYNC) +-LX_VALUE(MS_NOATIME) +-LX_VALUE(MS_NODIRATIME) ++LX_VALUE(SB_RDONLY) ++LX_VALUE(SB_SYNCHRONOUS) ++LX_VALUE(SB_MANDLOCK) ++LX_VALUE(SB_DIRSYNC) ++LX_VALUE(SB_NOATIME) ++LX_VALUE(SB_NODIRATIME) + + /* linux/mount.h */ + LX_VALUE(MNT_NOSUID) +--- a/scripts/gdb/linux/proc.py ++++ b/scripts/gdb/linux/proc.py +@@ -114,11 +114,11 @@ def info_opts(lst, opt): + return opts + + +-FS_INFO = {constants.LX_MS_SYNCHRONOUS: ",sync", +- constants.LX_MS_MANDLOCK: ",mand", +- constants.LX_MS_DIRSYNC: ",dirsync", +- constants.LX_MS_NOATIME: ",noatime", +- constants.LX_MS_NODIRATIME: ",nodiratime"} ++FS_INFO = {constants.LX_SB_SYNCHRONOUS: ",sync", ++ constants.LX_SB_MANDLOCK: ",mand", ++ constants.LX_SB_DIRSYNC: ",dirsync", ++ constants.LX_SB_NOATIME: ",noatime", ++ constants.LX_SB_NODIRATIME: ",nodiratime"} + + MNT_INFO = {constants.LX_MNT_NOSUID: ",nosuid", + constants.LX_MNT_NODEV: ",nodev", +@@ -184,7 +184,7 @@ values of that process namespace""" + fstype = superblock['s_type']['name'].string() + s_flags = int(superblock['s_flags']) + m_flags = int(vfs['mnt']['mnt_flags']) +- rd = "ro" if (s_flags & constants.LX_MS_RDONLY) else "rw" ++ rd = "ro" if (s_flags & constants.LX_SB_RDONLY) else "rw" + + gdb.write( + "{} {} {} {}{}{} 0 0\n" diff --git a/queue-5.0/series b/queue-5.0/series index 1981c555d46..796e33813ab 100644 --- a/queue-5.0/series +++ b/queue-5.0/series @@ -14,3 +14,8 @@ drm-disable-uncached-dma-optimization-for-arm-and-ar.patch media-revert-media-rc-some-events-are-dropped-by-userspace.patch revert-pci-pme-implement-runtime-pm-callbacks.patch bpf-stop-the-psock-parser-before-canceling-its-work.patch +gfs2-fix-missed-wakeups-in-find_insert_glock.patch +staging-erofs-keep-corrupted-fs-from-crashing-kernel-in-erofs_namei.patch +staging-erofs-compressed_pages-should-not-be-accessed-again-after-freed.patch +scripts-gdb-replace-flags-ms_xyz-sb_xyz.patch +ath9k-avoid-of-no-eeprom-quirks-without-qca-no-eeprom.patch diff --git a/queue-5.0/staging-erofs-compressed_pages-should-not-be-accessed-again-after-freed.patch b/queue-5.0/staging-erofs-compressed_pages-should-not-be-accessed-again-after-freed.patch new file mode 100644 index 00000000000..663efe1359d --- /dev/null +++ b/queue-5.0/staging-erofs-compressed_pages-should-not-be-accessed-again-after-freed.patch @@ -0,0 +1,176 @@ +From af692e117cb8cd9d3d844d413095775abc1217f9 Mon Sep 17 00:00:00 2001 +From: Gao Xiang +Date: Wed, 27 Feb 2019 13:33:30 +0800 +Subject: staging: erofs: compressed_pages should not be accessed again after freed + +From: Gao Xiang + +commit af692e117cb8cd9d3d844d413095775abc1217f9 upstream. + +This patch resolves the following page use-after-free issue, +z_erofs_vle_unzip: + ... + for (i = 0; i < nr_pages; ++i) { + ... + z_erofs_onlinepage_endio(page); (1) + } + + for (i = 0; i < clusterpages; ++i) { + page = compressed_pages[i]; + + if (page->mapping == mngda) (2) + continue; + /* recycle all individual staging pages */ + (void)z_erofs_gather_if_stagingpage(page_pool, page); (3) + WRITE_ONCE(compressed_pages[i], NULL); + } + ... + +After (1) is executed, page is freed and could be then reused, if +compressed_pages is scanned after that, it could fall info (2) or +(3) by mistake and that could finally be in a mess. + +This patch aims to solve the above issue only with little changes +as much as possible in order to make the fix backport easier. + +Fixes: 3883a79abd02 ("staging: erofs: introduce VLE decompression support") +Cc: # 4.19+ +Signed-off-by: Gao Xiang +Reviewed-by: Chao Yu +Signed-off-by: Greg Kroah-Hartman + + +--- + drivers/staging/erofs/unzip_vle.c | 38 +++++++++++++++++----------------- + drivers/staging/erofs/unzip_vle.h | 3 -- + drivers/staging/erofs/unzip_vle_lz4.c | 19 +++++++---------- + 3 files changed, 29 insertions(+), 31 deletions(-) + +--- a/drivers/staging/erofs/unzip_vle.c ++++ b/drivers/staging/erofs/unzip_vle.c +@@ -1017,11 +1017,10 @@ repeat: + if (llen > grp->llen) + llen = grp->llen; + +- err = z_erofs_vle_unzip_fast_percpu(compressed_pages, +- clusterpages, pages, llen, work->pageofs, +- z_erofs_onlinepage_endio); ++ err = z_erofs_vle_unzip_fast_percpu(compressed_pages, clusterpages, ++ pages, llen, work->pageofs); + if (err != -ENOTSUPP) +- goto out_percpu; ++ goto out; + + if (sparsemem_pages >= nr_pages) + goto skip_allocpage; +@@ -1042,8 +1041,25 @@ skip_allocpage: + erofs_vunmap(vout, nr_pages); + + out: ++ /* must handle all compressed pages before endding pages */ ++ for (i = 0; i < clusterpages; ++i) { ++ page = compressed_pages[i]; ++ ++#ifdef EROFS_FS_HAS_MANAGED_CACHE ++ if (page->mapping == MNGD_MAPPING(sbi)) ++ continue; ++#endif ++ /* recycle all individual staging pages */ ++ (void)z_erofs_gather_if_stagingpage(page_pool, page); ++ ++ WRITE_ONCE(compressed_pages[i], NULL); ++ } ++ + for (i = 0; i < nr_pages; ++i) { + page = pages[i]; ++ if (!page) ++ continue; ++ + DBG_BUGON(!page->mapping); + + /* recycle all individual staging pages */ +@@ -1056,20 +1072,6 @@ out: + z_erofs_onlinepage_endio(page); + } + +-out_percpu: +- for (i = 0; i < clusterpages; ++i) { +- page = compressed_pages[i]; +- +-#ifdef EROFS_FS_HAS_MANAGED_CACHE +- if (page->mapping == MNGD_MAPPING(sbi)) +- continue; +-#endif +- /* recycle all individual staging pages */ +- (void)z_erofs_gather_if_stagingpage(page_pool, page); +- +- WRITE_ONCE(compressed_pages[i], NULL); +- } +- + if (pages == z_pagemap_global) + mutex_unlock(&z_pagemap_global_lock); + else if (unlikely(pages != pages_onstack)) +--- a/drivers/staging/erofs/unzip_vle.h ++++ b/drivers/staging/erofs/unzip_vle.h +@@ -218,8 +218,7 @@ extern int z_erofs_vle_plain_copy(struct + + extern int z_erofs_vle_unzip_fast_percpu(struct page **compressed_pages, + unsigned clusterpages, struct page **pages, +- unsigned outlen, unsigned short pageofs, +- void (*endio)(struct page *)); ++ unsigned int outlen, unsigned short pageofs); + + extern int z_erofs_vle_unzip_vmap(struct page **compressed_pages, + unsigned clusterpages, void *vaddr, unsigned llen, +--- a/drivers/staging/erofs/unzip_vle_lz4.c ++++ b/drivers/staging/erofs/unzip_vle_lz4.c +@@ -125,8 +125,7 @@ int z_erofs_vle_unzip_fast_percpu(struct + unsigned int clusterpages, + struct page **pages, + unsigned int outlen, +- unsigned short pageofs, +- void (*endio)(struct page *)) ++ unsigned short pageofs) + { + void *vin, *vout; + unsigned int nr_pages, i, j; +@@ -148,19 +147,16 @@ int z_erofs_vle_unzip_fast_percpu(struct + ret = z_erofs_unzip_lz4(vin, vout + pageofs, + clusterpages * PAGE_SIZE, outlen); + +- if (ret >= 0) { +- outlen = ret; +- ret = 0; +- } ++ if (ret < 0) ++ goto out; ++ ret = 0; + + for (i = 0; i < nr_pages; ++i) { + j = min((unsigned int)PAGE_SIZE - pageofs, outlen); + + if (pages[i]) { +- if (ret < 0) { +- SetPageError(pages[i]); +- } else if (clusterpages == 1 && +- pages[i] == compressed_pages[0]) { ++ if (clusterpages == 1 && ++ pages[i] == compressed_pages[0]) { + memcpy(vin + pageofs, vout + pageofs, j); + } else { + void *dst = kmap_atomic(pages[i]); +@@ -168,12 +164,13 @@ int z_erofs_vle_unzip_fast_percpu(struct + memcpy(dst + pageofs, vout + pageofs, j); + kunmap_atomic(dst); + } +- endio(pages[i]); + } + vout += PAGE_SIZE; + outlen -= j; + pageofs = 0; + } ++ ++out: + preempt_enable(); + + if (clusterpages == 1) diff --git a/queue-5.0/staging-erofs-keep-corrupted-fs-from-crashing-kernel-in-erofs_namei.patch b/queue-5.0/staging-erofs-keep-corrupted-fs-from-crashing-kernel-in-erofs_namei.patch new file mode 100644 index 00000000000..e17418af0a5 --- /dev/null +++ b/queue-5.0/staging-erofs-keep-corrupted-fs-from-crashing-kernel-in-erofs_namei.patch @@ -0,0 +1,314 @@ +From 419d6efc50e94bcf5d6b35cd8c71f79edadec564 Mon Sep 17 00:00:00 2001 +From: Gao Xiang +Date: Fri, 1 Feb 2019 20:16:31 +0800 +Subject: staging: erofs: keep corrupted fs from crashing kernel in erofs_namei() + +From: Gao Xiang + +commit 419d6efc50e94bcf5d6b35cd8c71f79edadec564 upstream. + +As Al pointed out, " +... and while we are at it, what happens to + unsigned int nameoff = le16_to_cpu(de[mid].nameoff); + unsigned int matched = min(startprfx, endprfx); + + struct qstr dname = QSTR_INIT(data + nameoff, + unlikely(mid >= ndirents - 1) ? + maxsize - nameoff : + le16_to_cpu(de[mid + 1].nameoff) - nameoff); + + /* string comparison without already matched prefix */ + int ret = dirnamecmp(name, &dname, &matched); +if le16_to_cpu(de[...].nameoff) is not monotonically increasing? I.e. +what's to prevent e.g. (unsigned)-1 ending up in dname.len? + +Corrupted fs image shouldn't oops the kernel.. " + +Revisit the related lookup flow to address the issue. + +Fixes: d72d1ce60174 ("staging: erofs: add namei functions") +Cc: # 4.19+ +Suggested-by: Al Viro +Signed-off-by: Gao Xiang +Signed-off-by: Greg Kroah-Hartman + + +--- + drivers/staging/erofs/namei.c | 183 ++++++++++++++++++++++-------------------- + 1 file changed, 97 insertions(+), 86 deletions(-) + +--- a/drivers/staging/erofs/namei.c ++++ b/drivers/staging/erofs/namei.c +@@ -15,74 +15,77 @@ + + #include + +-/* based on the value of qn->len is accurate */ +-static inline int dirnamecmp(struct qstr *qn, +- struct qstr *qd, unsigned int *matched) ++struct erofs_qstr { ++ const unsigned char *name; ++ const unsigned char *end; ++}; ++ ++/* based on the end of qn is accurate and it must have the trailing '\0' */ ++static inline int dirnamecmp(const struct erofs_qstr *qn, ++ const struct erofs_qstr *qd, ++ unsigned int *matched) + { +- unsigned int i = *matched, len = min(qn->len, qd->len); +-loop: +- if (unlikely(i >= len)) { +- *matched = i; +- if (qn->len < qd->len) { +- /* +- * actually (qn->len == qd->len) +- * when qd->name[i] == '\0' +- */ +- return qd->name[i] == '\0' ? 0 : -1; +- } +- return (qn->len > qd->len); +- } ++ unsigned int i = *matched; + +- if (qn->name[i] != qd->name[i]) { +- *matched = i; +- return qn->name[i] > qd->name[i] ? 1 : -1; ++ /* ++ * on-disk error, let's only BUG_ON in the debugging mode. ++ * otherwise, it will return 1 to just skip the invalid name ++ * and go on (in consideration of the lookup performance). ++ */ ++ DBG_BUGON(qd->name > qd->end); ++ ++ /* qd could not have trailing '\0' */ ++ /* However it is absolutely safe if < qd->end */ ++ while (qd->name + i < qd->end && qd->name[i] != '\0') { ++ if (qn->name[i] != qd->name[i]) { ++ *matched = i; ++ return qn->name[i] > qd->name[i] ? 1 : -1; ++ } ++ ++i; + } +- +- ++i; +- goto loop; ++ *matched = i; ++ /* See comments in __d_alloc on the terminating NUL character */ ++ return qn->name[i] == '\0' ? 0 : 1; + } + +-static struct erofs_dirent *find_target_dirent( +- struct qstr *name, +- u8 *data, int maxsize) ++#define nameoff_from_disk(off, sz) (le16_to_cpu(off) & ((sz) - 1)) ++ ++static struct erofs_dirent *find_target_dirent(struct erofs_qstr *name, ++ u8 *data, ++ unsigned int dirblksize, ++ const int ndirents) + { +- unsigned int ndirents, head, back; ++ int head, back; + unsigned int startprfx, endprfx; + struct erofs_dirent *const de = (struct erofs_dirent *)data; + +- /* make sure that maxsize is valid */ +- BUG_ON(maxsize < sizeof(struct erofs_dirent)); +- +- ndirents = le16_to_cpu(de->nameoff) / sizeof(*de); +- +- /* corrupted dir (may be unnecessary...) */ +- BUG_ON(!ndirents); +- +- head = 0; ++ /* since the 1st dirent has been evaluated previously */ ++ head = 1; + back = ndirents - 1; + startprfx = endprfx = 0; + + while (head <= back) { +- unsigned int mid = head + (back - head) / 2; +- unsigned int nameoff = le16_to_cpu(de[mid].nameoff); ++ const int mid = head + (back - head) / 2; ++ const int nameoff = nameoff_from_disk(de[mid].nameoff, ++ dirblksize); + unsigned int matched = min(startprfx, endprfx); +- +- struct qstr dname = QSTR_INIT(data + nameoff, +- unlikely(mid >= ndirents - 1) ? +- maxsize - nameoff : +- le16_to_cpu(de[mid + 1].nameoff) - nameoff); ++ struct erofs_qstr dname = { ++ .name = data + nameoff, ++ .end = unlikely(mid >= ndirents - 1) ? ++ data + dirblksize : ++ data + nameoff_from_disk(de[mid + 1].nameoff, ++ dirblksize) ++ }; + + /* string comparison without already matched prefix */ + int ret = dirnamecmp(name, &dname, &matched); + +- if (unlikely(!ret)) ++ if (unlikely(!ret)) { + return de + mid; +- else if (ret > 0) { ++ } else if (ret > 0) { + head = mid + 1; + startprfx = matched; +- } else if (unlikely(mid < 1)) /* fix "mid" overflow */ +- break; +- else { ++ } else { + back = mid - 1; + endprfx = matched; + } +@@ -91,12 +94,12 @@ static struct erofs_dirent *find_target_ + return ERR_PTR(-ENOENT); + } + +-static struct page *find_target_block_classic( +- struct inode *dir, +- struct qstr *name, int *_diff) ++static struct page *find_target_block_classic(struct inode *dir, ++ struct erofs_qstr *name, ++ int *_ndirents) + { + unsigned int startprfx, endprfx; +- unsigned int head, back; ++ int head, back; + struct address_space *const mapping = dir->i_mapping; + struct page *candidate = ERR_PTR(-ENOENT); + +@@ -105,41 +108,43 @@ static struct page *find_target_block_cl + back = inode_datablocks(dir) - 1; + + while (head <= back) { +- unsigned int mid = head + (back - head) / 2; ++ const int mid = head + (back - head) / 2; + struct page *page = read_mapping_page(mapping, mid, NULL); + +- if (IS_ERR(page)) { +-exact_out: +- if (!IS_ERR(candidate)) /* valid candidate */ +- put_page(candidate); +- return page; +- } else { +- int diff; +- unsigned int ndirents, matched; +- struct qstr dname; ++ if (!IS_ERR(page)) { + struct erofs_dirent *de = kmap_atomic(page); +- unsigned int nameoff = le16_to_cpu(de->nameoff); +- +- ndirents = nameoff / sizeof(*de); ++ const int nameoff = nameoff_from_disk(de->nameoff, ++ EROFS_BLKSIZ); ++ const int ndirents = nameoff / sizeof(*de); ++ int diff; ++ unsigned int matched; ++ struct erofs_qstr dname; + +- /* corrupted dir (should have one entry at least) */ +- BUG_ON(!ndirents || nameoff > PAGE_SIZE); ++ if (unlikely(!ndirents)) { ++ DBG_BUGON(1); ++ kunmap_atomic(de); ++ put_page(page); ++ page = ERR_PTR(-EIO); ++ goto out; ++ } + + matched = min(startprfx, endprfx); + + dname.name = (u8 *)de + nameoff; +- dname.len = ndirents == 1 ? +- /* since the rest of the last page is 0 */ +- EROFS_BLKSIZ - nameoff +- : le16_to_cpu(de[1].nameoff) - nameoff; ++ if (ndirents == 1) ++ dname.end = (u8 *)de + EROFS_BLKSIZ; ++ else ++ dname.end = (u8 *)de + ++ nameoff_from_disk(de[1].nameoff, ++ EROFS_BLKSIZ); + + /* string comparison without already matched prefix */ + diff = dirnamecmp(name, &dname, &matched); + kunmap_atomic(de); + + if (unlikely(!diff)) { +- *_diff = 0; +- goto exact_out; ++ *_ndirents = 0; ++ goto out; + } else if (diff > 0) { + head = mid + 1; + startprfx = matched; +@@ -147,45 +152,51 @@ exact_out: + if (likely(!IS_ERR(candidate))) + put_page(candidate); + candidate = page; ++ *_ndirents = ndirents; + } else { + put_page(page); + +- if (unlikely(mid < 1)) /* fix "mid" overflow */ +- break; +- + back = mid - 1; + endprfx = matched; + } ++ continue; + } ++out: /* free if the candidate is valid */ ++ if (!IS_ERR(candidate)) ++ put_page(candidate); ++ return page; + } +- *_diff = 1; + return candidate; + } + + int erofs_namei(struct inode *dir, +- struct qstr *name, +- erofs_nid_t *nid, unsigned int *d_type) ++ struct qstr *name, ++ erofs_nid_t *nid, unsigned int *d_type) + { +- int diff; ++ int ndirents; + struct page *page; +- u8 *data; ++ void *data; + struct erofs_dirent *de; ++ struct erofs_qstr qn; + + if (unlikely(!dir->i_size)) + return -ENOENT; + +- diff = 1; +- page = find_target_block_classic(dir, name, &diff); ++ qn.name = name->name; ++ qn.end = name->name + name->len; ++ ++ ndirents = 0; ++ page = find_target_block_classic(dir, &qn, &ndirents); + + if (unlikely(IS_ERR(page))) + return PTR_ERR(page); + + data = kmap_atomic(page); + /* the target page has been mapped */ +- de = likely(diff) ? +- /* since the rest of the last page is 0 */ +- find_target_dirent(name, data, EROFS_BLKSIZ) : +- (struct erofs_dirent *)data; ++ if (ndirents) ++ de = find_target_dirent(&qn, data, EROFS_BLKSIZ, ndirents); ++ else ++ de = (struct erofs_dirent *)data; + + if (likely(!IS_ERR(de))) { + *nid = le64_to_cpu(de->nid);