From: Greg Kroah-Hartman Date: Tue, 12 Mar 2019 12:57:36 +0000 (-0700) Subject: 4.19-stable patches X-Git-Tag: v5.0.2~14 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=6ebfa1bb02cb8ee2120b896ca8d6959cf3944cf7;p=thirdparty%2Fkernel%2Fstable-queue.git 4.19-stable patches added patches: ath9k-avoid-of-no-eeprom-quirks-without-qca-no-eeprom.patch cifs-allow-calling-smb2_xxx_free-null.patch driver-core-postpone-dma-tear-down-until-after-devres-release.patch gfs2-fix-missed-wakeups-in-find_insert_glock.patch staging-erofs-add-error-handling-for-xattr-submodule.patch staging-erofs-fix-fast-symlink-w-o-xattr-when-fs-xattr-is-on.patch staging-erofs-fix-memleak-of-inode-s-shared-xattr-array.patch staging-erofs-fix-race-of-initializing-xattrs-of-a-inode-at-the-same-time.patch staging-erofs-keep-corrupted-fs-from-crashing-kernel-in-erofs_namei.patch --- diff --git a/queue-4.19/ath9k-avoid-of-no-eeprom-quirks-without-qca-no-eeprom.patch b/queue-4.19/ath9k-avoid-of-no-eeprom-quirks-without-qca-no-eeprom.patch new file mode 100644 index 00000000000..88a834cbf3f --- /dev/null +++ b/queue-4.19/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-4.19/cifs-allow-calling-smb2_xxx_free-null.patch b/queue-4.19/cifs-allow-calling-smb2_xxx_free-null.patch new file mode 100644 index 00000000000..4aa54ab6455 --- /dev/null +++ b/queue-4.19/cifs-allow-calling-smb2_xxx_free-null.patch @@ -0,0 +1,73 @@ +From 32a1fb36f6e50183871c2c1fcf5493c633e84732 Mon Sep 17 00:00:00 2001 +From: Ronnie Sahlberg +Date: Wed, 24 Oct 2018 11:50:33 +1000 +Subject: cifs: allow calling SMB2_xxx_free(NULL) + +From: Ronnie Sahlberg + +commit 32a1fb36f6e50183871c2c1fcf5493c633e84732 upstream. + +Change these free functions to allow passing NULL as the argument and +treat it as a no-op just like free(NULL) would. +Or, if rqst->rq_iov is NULL. + +The second scenario could happen for smb2_queryfs() if the call +to SMB2_query_info_init() fails and we go to qfs_exit to clean up +and free all resources. +In that case we have not yet assigned rqst[2].rq_iov and thus +the rq_iov dereference in SMB2_close_free() will cause a NULL pointer +dereference. + +[ bp: upstream patch also fixes SMB2_set_info_free which was introduced in 4.20 ] + +Fixes: 1eb9fb52040f ("cifs: create SMB2_open_init()/SMB2_open_free() helpers") + +Signed-off-by: Ronnie Sahlberg +Signed-off-by: Steve French +Reviewed-by: Aurelien Aptel +CC: Stable +Signed-off-by: Greg Kroah-Hartman + +--- + fs/cifs/smb2pdu.c | 16 ++++++++++------ + 1 file changed, 10 insertions(+), 6 deletions(-) + +--- a/fs/cifs/smb2pdu.c ++++ b/fs/cifs/smb2pdu.c +@@ -2243,10 +2243,12 @@ SMB2_open_free(struct smb_rqst *rqst) + { + int i; + +- cifs_small_buf_release(rqst->rq_iov[0].iov_base); +- for (i = 1; i < rqst->rq_nvec; i++) +- if (rqst->rq_iov[i].iov_base != smb2_padding) +- kfree(rqst->rq_iov[i].iov_base); ++ if (rqst && rqst->rq_iov) { ++ cifs_small_buf_release(rqst->rq_iov[0].iov_base); ++ for (i = 1; i < rqst->rq_nvec; i++) ++ if (rqst->rq_iov[i].iov_base != smb2_padding) ++ kfree(rqst->rq_iov[i].iov_base); ++ } + } + + int +@@ -2535,7 +2537,8 @@ SMB2_close_init(struct cifs_tcon *tcon, + void + SMB2_close_free(struct smb_rqst *rqst) + { +- cifs_small_buf_release(rqst->rq_iov[0].iov_base); /* request */ ++ if (rqst && rqst->rq_iov) ++ cifs_small_buf_release(rqst->rq_iov[0].iov_base); /* request */ + } + + int +@@ -2685,7 +2688,8 @@ SMB2_query_info_init(struct cifs_tcon *t + void + SMB2_query_info_free(struct smb_rqst *rqst) + { +- cifs_small_buf_release(rqst->rq_iov[0].iov_base); /* request */ ++ if (rqst && rqst->rq_iov) ++ cifs_small_buf_release(rqst->rq_iov[0].iov_base); /* request */ + } + + static int diff --git a/queue-4.19/driver-core-postpone-dma-tear-down-until-after-devres-release.patch b/queue-4.19/driver-core-postpone-dma-tear-down-until-after-devres-release.patch new file mode 100644 index 00000000000..9244c13262b --- /dev/null +++ b/queue-4.19/driver-core-postpone-dma-tear-down-until-after-devres-release.patch @@ -0,0 +1,119 @@ +From 376991db4b6464e906d699ef07681e2ffa8ab08c Mon Sep 17 00:00:00 2001 +From: Geert Uytterhoeven +Date: Thu, 7 Feb 2019 20:36:53 +0100 +Subject: driver core: Postpone DMA tear-down until after devres release +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +From: Geert Uytterhoeven + +commit 376991db4b6464e906d699ef07681e2ffa8ab08c upstream. + +When unbinding the (IOMMU-enabled) R-Car SATA device on Salvator-XS +(R-Car H3 ES2.0), in preparation of rebinding against vfio-platform for +device pass-through for virtualization: + +    echo ee300000.sata > /sys/bus/platform/drivers/sata_rcar/unbind + +the kernel crashes with: + +    Unable to handle kernel paging request at virtual address ffffffbf029ffffc +    Mem abort info: +      ESR = 0x96000006 +      Exception class = DABT (current EL), IL = 32 bits +      SET = 0, FnV = 0 +      EA = 0, S1PTW = 0 +    Data abort info: +      ISV = 0, ISS = 0x00000006 +      CM = 0, WnR = 0 +    swapper pgtable: 4k pages, 39-bit VAs, pgdp = 000000007e8c586c +    [ffffffbf029ffffc] pgd=000000073bfc6003, pud=000000073bfc6003, pmd=0000000000000000 +    Internal error: Oops: 96000006 [#1] SMP +    Modules linked in: +    CPU: 0 PID: 1098 Comm: bash Not tainted 5.0.0-rc5-salvator-x-00452-g37596f884f4318ef #287 +    Hardware name: Renesas Salvator-X 2nd version board based on r8a7795 ES2.0+ (DT) +    pstate: 60400005 (nZCv daif +PAN -UAO) +    pc : __free_pages+0x8/0x58 +    lr : __dma_direct_free_pages+0x50/0x5c +    sp : ffffff801268baa0 +    x29: ffffff801268baa0 x28: 0000000000000000 +    x27: ffffffc6f9c60bf0 x26: ffffffc6f9c60bf0 +    x25: ffffffc6f9c60810 x24: 0000000000000000 +    x23: 00000000fffff000 x22: ffffff8012145000 +    x21: 0000000000000800 x20: ffffffbf029fffc8 +    x19: 0000000000000000 x18: ffffffc6f86c42c8 +    x17: 0000000000000000 x16: 0000000000000070 +    x15: 0000000000000003 x14: 0000000000000000 +    x13: ffffff801103d7f8 x12: 0000000000000028 +    x11: ffffff8011117604 x10: 0000000000009ad8 +    x9 : ffffff80110126d0 x8 : ffffffc6f7563000 +    x7 : 6b6b6b6b6b6b6b6b x6 : 0000000000000018 +    x5 : ffffff8011cf3cc8 x4 : 0000000000004000 +    x3 : 0000000000080000 x2 : 0000000000000001 +    x1 : 0000000000000000 x0 : ffffffbf029fffc8 +    Process bash (pid: 1098, stack limit = 0x00000000c38e3e32) +    Call trace: +     __free_pages+0x8/0x58 +     __dma_direct_free_pages+0x50/0x5c +     arch_dma_free+0x1c/0x98 +     dma_direct_free+0x14/0x24 +     dma_free_attrs+0x9c/0xdc +     dmam_release+0x18/0x20 +     release_nodes+0x25c/0x28c +     devres_release_all+0x48/0x4c +     device_release_driver_internal+0x184/0x1f0 +     device_release_driver+0x14/0x1c +     unbind_store+0x70/0xb8 +     drv_attr_store+0x24/0x34 +     sysfs_kf_write+0x4c/0x64 +     kernfs_fop_write+0x154/0x1c4 +     __vfs_write+0x34/0x164 +     vfs_write+0xb4/0x16c +     ksys_write+0x5c/0xbc +     __arm64_sys_write+0x14/0x1c +     el0_svc_common+0x98/0x114 +     el0_svc_handler+0x1c/0x24 +     el0_svc+0x8/0xc +    Code: d51b4234 17fffffa a9bf7bfd 910003fd (b9403404) +    ---[ end trace 8c564cdd3a1a840f ]--- + +While I've bisected this to commit e8e683ae9a736407 ("iommu/of: Fix +probe-deferral"), and reverting that commit on post-v5.0-rc4 kernels +does fix the problem, this turned out to be a red herring. + +On arm64, arch_teardown_dma_ops() resets dev->dma_ops to NULL. +Hence if a driver has used a managed DMA allocation API, the allocated +DMA memory will be freed using the direct DMA ops, while it may have +been allocated using a custom DMA ops (iommu_dma_ops in this case). + +Fix this by reversing the order of the calls to devres_release_all() and +arch_teardown_dma_ops(). + +Signed-off-by: Geert Uytterhoeven +Acked-by: Christoph Hellwig +Reviewed-by: Rafael J. Wysocki +Cc: stable +Reviewed-by: Robin Murphy +[rm: backport for 4.12-4.19 - kernels before 5.0 will not see + the crash above, but may get silent memory corruption instead] +Signed-off-by: Robin Murphy +Signed-off-by: Greg Kroah-Hartman + +--- + drivers/base/dd.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/base/dd.c ++++ b/drivers/base/dd.c +@@ -963,9 +963,9 @@ static void __device_release_driver(stru + drv->remove(dev); + + device_links_driver_cleanup(dev); +- dma_deconfigure(dev); + + devres_release_all(dev); ++ dma_deconfigure(dev); + dev->driver = NULL; + dev_set_drvdata(dev, NULL); + if (dev->pm_domain && dev->pm_domain->dismiss) diff --git a/queue-4.19/gfs2-fix-missed-wakeups-in-find_insert_glock.patch b/queue-4.19/gfs2-fix-missed-wakeups-in-find_insert_glock.patch new file mode 100644 index 00000000000..29404caa7fe --- /dev/null +++ b/queue-4.19/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-4.19/series b/queue-4.19/series index fefe347bc85..f7ca9ddac0b 100644 --- a/queue-4.19/series +++ b/queue-4.19/series @@ -134,3 +134,12 @@ arm-dts-exynos-fix-max-voltage-for-buck8-regulator-on-odroid-xu3-xu4.patch drm-disable-uncached-dma-optimization-for-arm-and-ar.patch netfilter-xt_tee-fix-wrong-interface-selection.patch netfilter-xt_tee-add-missing-code-to-get-interface-i.patch +gfs2-fix-missed-wakeups-in-find_insert_glock.patch +staging-erofs-add-error-handling-for-xattr-submodule.patch +staging-erofs-fix-fast-symlink-w-o-xattr-when-fs-xattr-is-on.patch +staging-erofs-fix-memleak-of-inode-s-shared-xattr-array.patch +staging-erofs-fix-race-of-initializing-xattrs-of-a-inode-at-the-same-time.patch +staging-erofs-keep-corrupted-fs-from-crashing-kernel-in-erofs_namei.patch +cifs-allow-calling-smb2_xxx_free-null.patch +ath9k-avoid-of-no-eeprom-quirks-without-qca-no-eeprom.patch +driver-core-postpone-dma-tear-down-until-after-devres-release.patch diff --git a/queue-4.19/staging-erofs-add-error-handling-for-xattr-submodule.patch b/queue-4.19/staging-erofs-add-error-handling-for-xattr-submodule.patch new file mode 100644 index 00000000000..9a57537e21b --- /dev/null +++ b/queue-4.19/staging-erofs-add-error-handling-for-xattr-submodule.patch @@ -0,0 +1,318 @@ +From foo@baz Tue Mar 12 05:46:41 PDT 2019 +From: Gao Xiang +Date: Mon, 11 Mar 2019 14:08:54 +0800 +Subject: staging: erofs: add error handling for xattr submodule +To: +Cc: Greg Kroah-Hartman , LKML , , Chao Yu , Chao Yu , Miao Xie , Fang Wei , Gao Xiang +Message-ID: <20190311060858.28654-1-gaoxiang25@huawei.com> + +From: Gao Xiang + +commit cadf1ccf1b0021d0b7a9347e102ac5258f9f98c8 upstream. + +This patch enhances the missing error handling code for +xattr submodule, which improves the stability for the rare cases. + +Reviewed-by: Chao Yu +Signed-off-by: Chao Yu +Signed-off-by: Gao Xiang +Signed-off-by: Greg Kroah-Hartman +--- + drivers/staging/erofs/internal.h | 5 + + drivers/staging/erofs/xattr.c | 112 +++++++++++++++++++++++++++------------ + 2 files changed, 81 insertions(+), 36 deletions(-) + +--- a/drivers/staging/erofs/internal.h ++++ b/drivers/staging/erofs/internal.h +@@ -485,8 +485,9 @@ struct erofs_map_blocks_iter { + }; + + +-static inline struct page *erofs_get_inline_page(struct inode *inode, +- erofs_blk_t blkaddr) ++static inline struct page * ++erofs_get_inline_page(struct inode *inode, ++ erofs_blk_t blkaddr) + { + return erofs_get_meta_page(inode->i_sb, + blkaddr, S_ISDIR(inode->i_mode)); +--- a/drivers/staging/erofs/xattr.c ++++ b/drivers/staging/erofs/xattr.c +@@ -24,16 +24,25 @@ struct xattr_iter { + + static inline void xattr_iter_end(struct xattr_iter *it, bool atomic) + { +- /* only init_inode_xattrs use non-atomic once */ ++ /* the only user of kunmap() is 'init_inode_xattrs' */ + if (unlikely(!atomic)) + kunmap(it->page); + else + kunmap_atomic(it->kaddr); ++ + unlock_page(it->page); + put_page(it->page); + } + +-static void init_inode_xattrs(struct inode *inode) ++static inline void xattr_iter_end_final(struct xattr_iter *it) ++{ ++ if (!it->page) ++ return; ++ ++ xattr_iter_end(it, true); ++} ++ ++static int init_inode_xattrs(struct inode *inode) + { + struct xattr_iter it; + unsigned i; +@@ -43,7 +52,7 @@ static void init_inode_xattrs(struct ino + bool atomic_map; + + if (likely(inode_has_inited_xattr(inode))) +- return; ++ return 0; + + vi = EROFS_V(inode); + BUG_ON(!vi->xattr_isize); +@@ -53,7 +62,8 @@ static void init_inode_xattrs(struct ino + it.ofs = erofs_blkoff(iloc(sbi, vi->nid) + vi->inode_isize); + + it.page = erofs_get_inline_page(inode, it.blkaddr); +- BUG_ON(IS_ERR(it.page)); ++ if (IS_ERR(it.page)) ++ return PTR_ERR(it.page); + + /* read in shared xattr array (non-atomic, see kmalloc below) */ + it.kaddr = kmap(it.page); +@@ -62,9 +72,12 @@ static void init_inode_xattrs(struct ino + ih = (struct erofs_xattr_ibody_header *)(it.kaddr + it.ofs); + + vi->xattr_shared_count = ih->h_shared_count; +- vi->xattr_shared_xattrs = (unsigned *)kmalloc_array( +- vi->xattr_shared_count, sizeof(unsigned), +- GFP_KERNEL | __GFP_NOFAIL); ++ vi->xattr_shared_xattrs = kmalloc_array(vi->xattr_shared_count, ++ sizeof(uint), GFP_KERNEL); ++ if (!vi->xattr_shared_xattrs) { ++ xattr_iter_end(&it, atomic_map); ++ return -ENOMEM; ++ } + + /* let's skip ibody header */ + it.ofs += sizeof(struct erofs_xattr_ibody_header); +@@ -77,7 +90,8 @@ static void init_inode_xattrs(struct ino + + it.page = erofs_get_meta_page(inode->i_sb, + ++it.blkaddr, S_ISDIR(inode->i_mode)); +- BUG_ON(IS_ERR(it.page)); ++ if (IS_ERR(it.page)) ++ return PTR_ERR(it.page); + + it.kaddr = kmap_atomic(it.page); + atomic_map = true; +@@ -90,6 +104,7 @@ static void init_inode_xattrs(struct ino + xattr_iter_end(&it, atomic_map); + + inode_set_inited_xattr(inode); ++ return 0; + } + + struct xattr_iter_handlers { +@@ -99,18 +114,25 @@ struct xattr_iter_handlers { + void (*value)(struct xattr_iter *, unsigned, char *, unsigned); + }; + +-static void xattr_iter_fixup(struct xattr_iter *it) ++static inline int xattr_iter_fixup(struct xattr_iter *it) + { +- if (unlikely(it->ofs >= EROFS_BLKSIZ)) { +- xattr_iter_end(it, true); ++ if (it->ofs < EROFS_BLKSIZ) ++ return 0; + +- it->blkaddr += erofs_blknr(it->ofs); +- it->page = erofs_get_meta_page(it->sb, it->blkaddr, false); +- BUG_ON(IS_ERR(it->page)); ++ xattr_iter_end(it, true); + +- it->kaddr = kmap_atomic(it->page); +- it->ofs = erofs_blkoff(it->ofs); ++ it->blkaddr += erofs_blknr(it->ofs); ++ it->page = erofs_get_meta_page(it->sb, it->blkaddr, false); ++ if (IS_ERR(it->page)) { ++ int err = PTR_ERR(it->page); ++ ++ it->page = NULL; ++ return err; + } ++ ++ it->kaddr = kmap_atomic(it->page); ++ it->ofs = erofs_blkoff(it->ofs); ++ return 0; + } + + static int inline_xattr_iter_begin(struct xattr_iter *it, +@@ -132,21 +154,24 @@ static int inline_xattr_iter_begin(struc + it->ofs = erofs_blkoff(iloc(sbi, vi->nid) + inline_xattr_ofs); + + it->page = erofs_get_inline_page(inode, it->blkaddr); +- BUG_ON(IS_ERR(it->page)); +- it->kaddr = kmap_atomic(it->page); ++ if (IS_ERR(it->page)) ++ return PTR_ERR(it->page); + ++ it->kaddr = kmap_atomic(it->page); + return vi->xattr_isize - xattr_header_sz; + } + + static int xattr_foreach(struct xattr_iter *it, +- struct xattr_iter_handlers *op, unsigned *tlimit) ++ const struct xattr_iter_handlers *op, unsigned int *tlimit) + { + struct erofs_xattr_entry entry; + unsigned value_sz, processed, slice; + int err; + + /* 0. fixup blkaddr, ofs, ipage */ +- xattr_iter_fixup(it); ++ err = xattr_iter_fixup(it); ++ if (err) ++ return err; + + /* + * 1. read xattr entry to the memory, +@@ -178,7 +203,9 @@ static int xattr_foreach(struct xattr_it + if (it->ofs >= EROFS_BLKSIZ) { + BUG_ON(it->ofs > EROFS_BLKSIZ); + +- xattr_iter_fixup(it); ++ err = xattr_iter_fixup(it); ++ if (err) ++ goto out; + it->ofs = 0; + } + +@@ -210,7 +237,10 @@ static int xattr_foreach(struct xattr_it + while (processed < value_sz) { + if (it->ofs >= EROFS_BLKSIZ) { + BUG_ON(it->ofs > EROFS_BLKSIZ); +- xattr_iter_fixup(it); ++ ++ err = xattr_iter_fixup(it); ++ if (err) ++ goto out; + it->ofs = 0; + } + +@@ -270,7 +300,7 @@ static void xattr_copyvalue(struct xattr + memcpy(it->buffer + processed, buf, len); + } + +-static struct xattr_iter_handlers find_xattr_handlers = { ++static const struct xattr_iter_handlers find_xattr_handlers = { + .entry = xattr_entrymatch, + .name = xattr_namematch, + .alloc_buffer = xattr_checkbuffer, +@@ -291,8 +321,11 @@ static int inline_getxattr(struct inode + ret = xattr_foreach(&it->it, &find_xattr_handlers, &remaining); + if (ret >= 0) + break; ++ ++ if (ret != -ENOATTR) /* -ENOMEM, -EIO, etc. */ ++ break; + } +- xattr_iter_end(&it->it, true); ++ xattr_iter_end_final(&it->it); + + return ret < 0 ? ret : it->buffer_size; + } +@@ -315,8 +348,10 @@ static int shared_getxattr(struct inode + xattr_iter_end(&it->it, true); + + it->it.page = erofs_get_meta_page(inode->i_sb, +- blkaddr, false); +- BUG_ON(IS_ERR(it->it.page)); ++ blkaddr, false); ++ if (IS_ERR(it->it.page)) ++ return PTR_ERR(it->it.page); ++ + it->it.kaddr = kmap_atomic(it->it.page); + it->it.blkaddr = blkaddr; + } +@@ -324,9 +359,12 @@ static int shared_getxattr(struct inode + ret = xattr_foreach(&it->it, &find_xattr_handlers, NULL); + if (ret >= 0) + break; ++ ++ if (ret != -ENOATTR) /* -ENOMEM, -EIO, etc. */ ++ break; + } + if (vi->xattr_shared_count) +- xattr_iter_end(&it->it, true); ++ xattr_iter_end_final(&it->it); + + return ret < 0 ? ret : it->buffer_size; + } +@@ -351,7 +389,9 @@ int erofs_getxattr(struct inode *inode, + if (unlikely(name == NULL)) + return -EINVAL; + +- init_inode_xattrs(inode); ++ ret = init_inode_xattrs(inode); ++ if (ret) ++ return ret; + + it.index = index; + +@@ -494,7 +534,7 @@ static int xattr_skipvalue(struct xattr_ + return 1; + } + +-static struct xattr_iter_handlers list_xattr_handlers = { ++static const struct xattr_iter_handlers list_xattr_handlers = { + .entry = xattr_entrylist, + .name = xattr_namelist, + .alloc_buffer = xattr_skipvalue, +@@ -516,7 +556,7 @@ static int inline_listxattr(struct listx + if (ret < 0) + break; + } +- xattr_iter_end(&it->it, true); ++ xattr_iter_end_final(&it->it); + return ret < 0 ? ret : it->buffer_ofs; + } + +@@ -538,8 +578,10 @@ static int shared_listxattr(struct listx + xattr_iter_end(&it->it, true); + + it->it.page = erofs_get_meta_page(inode->i_sb, +- blkaddr, false); +- BUG_ON(IS_ERR(it->it.page)); ++ blkaddr, false); ++ if (IS_ERR(it->it.page)) ++ return PTR_ERR(it->it.page); ++ + it->it.kaddr = kmap_atomic(it->it.page); + it->it.blkaddr = blkaddr; + } +@@ -549,7 +591,7 @@ static int shared_listxattr(struct listx + break; + } + if (vi->xattr_shared_count) +- xattr_iter_end(&it->it, true); ++ xattr_iter_end_final(&it->it); + + return ret < 0 ? ret : it->buffer_ofs; + } +@@ -560,7 +602,9 @@ ssize_t erofs_listxattr(struct dentry *d + int ret; + struct listxattr_iter it; + +- init_inode_xattrs(d_inode(dentry)); ++ ret = init_inode_xattrs(d_inode(dentry)); ++ if (ret) ++ return ret; + + it.dentry = dentry; + it.buffer = buffer; diff --git a/queue-4.19/staging-erofs-fix-fast-symlink-w-o-xattr-when-fs-xattr-is-on.patch b/queue-4.19/staging-erofs-fix-fast-symlink-w-o-xattr-when-fs-xattr-is-on.patch new file mode 100644 index 00000000000..7b18aeb683a --- /dev/null +++ b/queue-4.19/staging-erofs-fix-fast-symlink-w-o-xattr-when-fs-xattr-is-on.patch @@ -0,0 +1,125 @@ +From foo@baz Tue Mar 12 05:46:41 PDT 2019 +From: Gao Xiang +Date: Mon, 11 Mar 2019 14:08:55 +0800 +Subject: staging: erofs: fix fast symlink w/o xattr when fs xattr is on +To: +Cc: Greg Kroah-Hartman , LKML , , Chao Yu , Chao Yu , Miao Xie , Fang Wei , Gao Xiang +Message-ID: <20190311060858.28654-2-gaoxiang25@huawei.com> + +From: Gao Xiang + +commit 7077fffcb0b0b65dc75e341306aeef4d0e7f2ec6 upstream. + +Currently, this will hit a BUG_ON for these symlinks as follows: + +- kernel message +------------[ cut here ]------------ +kernel BUG at drivers/staging/erofs/xattr.c:59! +SMP PTI +CPU: 1 PID: 1170 Comm: getllxattr Not tainted 4.20.0-rc6+ #92 +Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.10.2-2.fc27 04/01/2014 +RIP: 0010:init_inode_xattrs+0x22b/0x270 +Code: 48 0f 45 ea f0 ff 4d 34 74 0d 41 83 4c 24 e0 01 31 c0 e9 00 fe ff ff 48 89 ef e8 e0 31 9e ff eb e9 89 e8 e9 ef fd ff ff 0f 0$ + <0f> 0b 48 89 ef e8 fb f6 9c ff 48 8b 45 08 a8 01 75 24 f0 ff 4d 34 +RSP: 0018:ffffa03ac026bdf8 EFLAGS: 00010246 +------------[ cut here ]------------ +... +Call Trace: + erofs_listxattr+0x30/0x2c0 + ? selinux_inode_listxattr+0x5a/0x80 + ? kmem_cache_alloc+0x33/0x170 + ? security_inode_listxattr+0x27/0x40 + listxattr+0xaf/0xc0 + path_listxattr+0x5a/0xa0 + do_syscall_64+0x43/0xf0 + entry_SYSCALL_64_after_hwframe+0x44/0xa9 +... +---[ end trace 3c24b49408dc0c72 ]--- + +Fix it by checking ->xattr_isize in init_inode_xattrs(), +and it also fixes improper return value -ENOTSUPP +(it should be -ENODATA if xattr is enabled) for those inodes. + +Fixes: b17500a0fdba ("staging: erofs: introduce xattr & acl support") +Cc: # 4.19+ +Reported-by: Li Guifu +Tested-by: Li Guifu +Reviewed-by: Chao Yu +Signed-off-by: Gao Xiang +Signed-off-by: Greg Kroah-Hartman +--- + drivers/staging/erofs/inode.c | 8 ++++---- + drivers/staging/erofs/xattr.c | 25 ++++++++++++++++++++----- + 2 files changed, 24 insertions(+), 9 deletions(-) + +--- a/drivers/staging/erofs/inode.c ++++ b/drivers/staging/erofs/inode.c +@@ -184,16 +184,16 @@ static int fill_inode(struct inode *inod + /* setup the new inode */ + if (S_ISREG(inode->i_mode)) { + #ifdef CONFIG_EROFS_FS_XATTR +- if (vi->xattr_isize) +- inode->i_op = &erofs_generic_xattr_iops; ++ inode->i_op = &erofs_generic_xattr_iops; + #endif + inode->i_fop = &generic_ro_fops; + } else if (S_ISDIR(inode->i_mode)) { + inode->i_op = + #ifdef CONFIG_EROFS_FS_XATTR +- vi->xattr_isize ? &erofs_dir_xattr_iops : +-#endif ++ &erofs_dir_xattr_iops; ++#else + &erofs_dir_iops; ++#endif + inode->i_fop = &erofs_dir_fops; + } else if (S_ISLNK(inode->i_mode)) { + /* by default, page_get_link is used for symlink */ +--- a/drivers/staging/erofs/xattr.c ++++ b/drivers/staging/erofs/xattr.c +@@ -55,7 +55,26 @@ static int init_inode_xattrs(struct inod + return 0; + + vi = EROFS_V(inode); +- BUG_ON(!vi->xattr_isize); ++ ++ /* ++ * bypass all xattr operations if ->xattr_isize is not greater than ++ * sizeof(struct erofs_xattr_ibody_header), in detail: ++ * 1) it is not enough to contain erofs_xattr_ibody_header then ++ * ->xattr_isize should be 0 (it means no xattr); ++ * 2) it is just to contain erofs_xattr_ibody_header, which is on-disk ++ * undefined right now (maybe use later with some new sb feature). ++ */ ++ if (vi->xattr_isize == sizeof(struct erofs_xattr_ibody_header)) { ++ errln("xattr_isize %d of nid %llu is not supported yet", ++ vi->xattr_isize, vi->nid); ++ return -ENOTSUPP; ++ } else if (vi->xattr_isize < sizeof(struct erofs_xattr_ibody_header)) { ++ if (unlikely(vi->xattr_isize)) { ++ DBG_BUGON(1); ++ return -EIO; /* xattr ondisk layout error */ ++ } ++ return -ENOATTR; ++ } + + sbi = EROFS_I_SB(inode); + it.blkaddr = erofs_blknr(iloc(sbi, vi->nid) + vi->inode_isize); +@@ -414,7 +433,6 @@ static int erofs_xattr_generic_get(const + struct dentry *unused, struct inode *inode, + const char *name, void *buffer, size_t size) + { +- struct erofs_vnode *const vi = EROFS_V(inode); + struct erofs_sb_info *const sbi = EROFS_I_SB(inode); + + switch (handler->flags) { +@@ -432,9 +450,6 @@ static int erofs_xattr_generic_get(const + return -EINVAL; + } + +- if (!vi->xattr_isize) +- return -ENOATTR; +- + return erofs_getxattr(inode, handler->flags, name, buffer, size); + } + diff --git a/queue-4.19/staging-erofs-fix-memleak-of-inode-s-shared-xattr-array.patch b/queue-4.19/staging-erofs-fix-memleak-of-inode-s-shared-xattr-array.patch new file mode 100644 index 00000000000..6bd6d15931c --- /dev/null +++ b/queue-4.19/staging-erofs-fix-memleak-of-inode-s-shared-xattr-array.patch @@ -0,0 +1,43 @@ +From foo@baz Tue Mar 12 05:46:41 PDT 2019 +From: Gao Xiang +Date: Mon, 11 Mar 2019 14:08:56 +0800 +Subject: staging: erofs: fix memleak of inode's shared xattr array +To: +Cc: Greg Kroah-Hartman , LKML , , Chao Yu , Chao Yu , Miao Xie , Fang Wei , Sheng Yong , Gao Xiang +Message-ID: <20190311060858.28654-3-gaoxiang25@huawei.com> + +From: Gao Xiang + +From: Sheng Yong + +commit 3b1b5291f79d040d549d7c746669fc30e8045b9b upstream. + +If it fails to read a shared xattr page, the inode's shared xattr array +is not freed. The next time the inode's xattr is accessed, the previously +allocated array is leaked. + +Signed-off-by: Sheng Yong +Fixes: b17500a0fdba ("staging: erofs: introduce xattr & acl support") +Cc: # 4.19+ +Reviewed-by: Gao Xiang +Signed-off-by: Gao Xiang +Signed-off-by: Greg Kroah-Hartman +--- + drivers/staging/erofs/xattr.c | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +--- a/drivers/staging/erofs/xattr.c ++++ b/drivers/staging/erofs/xattr.c +@@ -109,8 +109,11 @@ static int init_inode_xattrs(struct inod + + it.page = erofs_get_meta_page(inode->i_sb, + ++it.blkaddr, S_ISDIR(inode->i_mode)); +- if (IS_ERR(it.page)) ++ if (IS_ERR(it.page)) { ++ kfree(vi->xattr_shared_xattrs); ++ vi->xattr_shared_xattrs = NULL; + return PTR_ERR(it.page); ++ } + + it.kaddr = kmap_atomic(it.page); + atomic_map = true; diff --git a/queue-4.19/staging-erofs-fix-race-of-initializing-xattrs-of-a-inode-at-the-same-time.patch b/queue-4.19/staging-erofs-fix-race-of-initializing-xattrs-of-a-inode-at-the-same-time.patch new file mode 100644 index 00000000000..a70c3ece3e7 --- /dev/null +++ b/queue-4.19/staging-erofs-fix-race-of-initializing-xattrs-of-a-inode-at-the-same-time.patch @@ -0,0 +1,148 @@ +From foo@baz Tue Mar 12 05:46:41 PDT 2019 +From: Gao Xiang +Date: Mon, 11 Mar 2019 14:08:57 +0800 +Subject: staging: erofs: fix race of initializing xattrs of a inode at the same time +To: +Cc: Greg Kroah-Hartman , LKML , , Chao Yu , Chao Yu , Miao Xie , Fang Wei , Gao Xiang +Message-ID: <20190311060858.28654-4-gaoxiang25@huawei.com> + +From: Gao Xiang + +commit 62dc45979f3f8cb0ea67302a93bff686f0c46c5a upstream. + +In real scenario, there could be several threads accessing xattrs +of the same xattr-uninitialized inode, and init_inode_xattrs() +almost at the same time. + +That's actually an unexpected behavior, this patch closes the race. + +Fixes: b17500a0fdba ("staging: erofs: introduce xattr & acl support") +Cc: # 4.19+ +Reviewed-by: Chao Yu +Signed-off-by: Gao Xiang +Signed-off-by: Greg Kroah-Hartman +--- + drivers/staging/erofs/internal.h | 11 +++++++--- + drivers/staging/erofs/xattr.c | 41 +++++++++++++++++++++++++++------------ + 2 files changed, 37 insertions(+), 15 deletions(-) + +--- a/drivers/staging/erofs/internal.h ++++ b/drivers/staging/erofs/internal.h +@@ -327,12 +327,17 @@ static inline erofs_off_t iloc(struct er + return blknr_to_addr(sbi->meta_blkaddr) + (nid << sbi->islotbits); + } + +-#define inode_set_inited_xattr(inode) (EROFS_V(inode)->flags |= 1) +-#define inode_has_inited_xattr(inode) (EROFS_V(inode)->flags & 1) ++/* atomic flag definitions */ ++#define EROFS_V_EA_INITED_BIT 0 ++ ++/* bitlock definitions (arranged in reverse order) */ ++#define EROFS_V_BL_XATTR_BIT (BITS_PER_LONG - 1) + + struct erofs_vnode { + erofs_nid_t nid; +- unsigned int flags; ++ ++ /* atomic flags (including bitlocks) */ ++ unsigned long flags; + + unsigned char data_mapping_mode; + /* inline size in bytes */ +--- a/drivers/staging/erofs/xattr.c ++++ b/drivers/staging/erofs/xattr.c +@@ -44,17 +44,24 @@ static inline void xattr_iter_end_final( + + static int init_inode_xattrs(struct inode *inode) + { ++ struct erofs_vnode *const vi = EROFS_V(inode); + struct xattr_iter it; + unsigned i; + struct erofs_xattr_ibody_header *ih; + struct erofs_sb_info *sbi; +- struct erofs_vnode *vi; + bool atomic_map; ++ int ret = 0; + +- if (likely(inode_has_inited_xattr(inode))) ++ /* the most case is that xattrs of this inode are initialized. */ ++ if (test_bit(EROFS_V_EA_INITED_BIT, &vi->flags)) + return 0; + +- vi = EROFS_V(inode); ++ if (wait_on_bit_lock(&vi->flags, EROFS_V_BL_XATTR_BIT, TASK_KILLABLE)) ++ return -ERESTARTSYS; ++ ++ /* someone has initialized xattrs for us? */ ++ if (test_bit(EROFS_V_EA_INITED_BIT, &vi->flags)) ++ goto out_unlock; + + /* + * bypass all xattr operations if ->xattr_isize is not greater than +@@ -67,13 +74,16 @@ static int init_inode_xattrs(struct inod + if (vi->xattr_isize == sizeof(struct erofs_xattr_ibody_header)) { + errln("xattr_isize %d of nid %llu is not supported yet", + vi->xattr_isize, vi->nid); +- return -ENOTSUPP; ++ ret = -ENOTSUPP; ++ goto out_unlock; + } else if (vi->xattr_isize < sizeof(struct erofs_xattr_ibody_header)) { + if (unlikely(vi->xattr_isize)) { + DBG_BUGON(1); +- return -EIO; /* xattr ondisk layout error */ ++ ret = -EIO; ++ goto out_unlock; /* xattr ondisk layout error */ + } +- return -ENOATTR; ++ ret = -ENOATTR; ++ goto out_unlock; + } + + sbi = EROFS_I_SB(inode); +@@ -81,8 +91,10 @@ static int init_inode_xattrs(struct inod + it.ofs = erofs_blkoff(iloc(sbi, vi->nid) + vi->inode_isize); + + it.page = erofs_get_inline_page(inode, it.blkaddr); +- if (IS_ERR(it.page)) +- return PTR_ERR(it.page); ++ if (IS_ERR(it.page)) { ++ ret = PTR_ERR(it.page); ++ goto out_unlock; ++ } + + /* read in shared xattr array (non-atomic, see kmalloc below) */ + it.kaddr = kmap(it.page); +@@ -95,7 +107,8 @@ static int init_inode_xattrs(struct inod + sizeof(uint), GFP_KERNEL); + if (!vi->xattr_shared_xattrs) { + xattr_iter_end(&it, atomic_map); +- return -ENOMEM; ++ ret = -ENOMEM; ++ goto out_unlock; + } + + /* let's skip ibody header */ +@@ -112,7 +125,8 @@ static int init_inode_xattrs(struct inod + if (IS_ERR(it.page)) { + kfree(vi->xattr_shared_xattrs); + vi->xattr_shared_xattrs = NULL; +- return PTR_ERR(it.page); ++ ret = PTR_ERR(it.page); ++ goto out_unlock; + } + + it.kaddr = kmap_atomic(it.page); +@@ -125,8 +139,11 @@ static int init_inode_xattrs(struct inod + } + xattr_iter_end(&it, atomic_map); + +- inode_set_inited_xattr(inode); +- return 0; ++ set_bit(EROFS_V_EA_INITED_BIT, &vi->flags); ++ ++out_unlock: ++ clear_and_wake_up_bit(EROFS_V_BL_XATTR_BIT, &vi->flags); ++ return ret; + } + + struct xattr_iter_handlers { diff --git a/queue-4.19/staging-erofs-keep-corrupted-fs-from-crashing-kernel-in-erofs_namei.patch b/queue-4.19/staging-erofs-keep-corrupted-fs-from-crashing-kernel-in-erofs_namei.patch new file mode 100644 index 00000000000..a424c5cbd52 --- /dev/null +++ b/queue-4.19/staging-erofs-keep-corrupted-fs-from-crashing-kernel-in-erofs_namei.patch @@ -0,0 +1,318 @@ +From foo@baz Tue Mar 12 05:46:41 PDT 2019 +From: Gao Xiang +Date: Mon, 11 Mar 2019 14:08:58 +0800 +Subject: staging: erofs: keep corrupted fs from crashing kernel in erofs_namei() +To: +Cc: Greg Kroah-Hartman , LKML , , Chao Yu , Chao Yu , Miao Xie , Fang Wei , Gao Xiang +Message-ID: <20190311060858.28654-5-gaoxiang25@huawei.com> + +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 | 189 ++++++++++++++++++++++-------------------- + 1 file changed, 100 insertions(+), 89 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 *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 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 ndirents, head, back; +- unsigned startprfx, endprfx; ++ 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 mid = head + (back - head) / 2; +- unsigned nameoff = le16_to_cpu(de[mid].nameoff); +- unsigned 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); ++ const int mid = head + (back - head) / 2; ++ const int nameoff = nameoff_from_disk(de[mid].nameoff, ++ dirblksize); ++ unsigned int matched = min(startprfx, endprfx); ++ 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 startprfx, endprfx; +- unsigned head, back; ++ unsigned int startprfx, endprfx; ++ 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 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 ndirents, matched; +- struct qstr dname; ++ if (!IS_ERR(page)) { + struct erofs_dirent *de = kmap_atomic(page); +- unsigned 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 *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);