Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

Revert "staging: erofs: keep corrupted fs from crashing kernel in erofs_namei()"

This reverts commit d4104c5e783f5d053b97268fb92001d785de7dd5.

Turns out it still needs some more work, I merged it to soon :(

Reported-by: Gao Xiang <gaoxiang25@huawei.com>
Reported-by: Dan Carpenter <dan.carpenter@oracle.com>
Cc: Al Viro <viro@ZenIV.linux.org.uk>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

+78 -89
+78 -89
drivers/staging/erofs/namei.c
··· 15 15 16 16 #include <trace/events/erofs.h> 17 17 18 - struct erofs_qstr { 19 - const unsigned char *name; 20 - const unsigned char *end; 21 - }; 22 - 23 - /* based on the end of qn is accurate and it must have the trailing '\0' */ 24 - static inline int dirnamecmp(const struct erofs_qstr *qn, 25 - const struct erofs_qstr *qd, 26 - unsigned int *matched) 18 + /* based on the value of qn->len is accurate */ 19 + static inline int dirnamecmp(struct qstr *qn, 20 + struct qstr *qd, unsigned int *matched) 27 21 { 28 - unsigned int i = *matched; 29 - 30 - /* 31 - * on-disk error, let's only BUG_ON in the debugging mode. 32 - * otherwise, it will return 1 to just skip the invalid name 33 - * and go on (in consideration of the lookup performance). 34 - */ 35 - DBG_BUGON(qd->name > qd->end); 36 - 37 - /* qd could not have trailing '\0' */ 38 - /* However it is absolutely safe if < qd->end */ 39 - while (qd->name + i < qd->end && qd->name[i] != '\0') { 40 - if (qn->name[i] != qd->name[i]) { 41 - *matched = i; 42 - return qn->name[i] > qd->name[i] ? 1 : -1; 22 + unsigned int i = *matched, len = min(qn->len, qd->len); 23 + loop: 24 + if (unlikely(i >= len)) { 25 + *matched = i; 26 + if (qn->len < qd->len) { 27 + /* 28 + * actually (qn->len == qd->len) 29 + * when qd->name[i] == '\0' 30 + */ 31 + return qd->name[i] == '\0' ? 0 : -1; 43 32 } 44 - ++i; 33 + return (qn->len > qd->len); 45 34 } 46 - *matched = i; 47 - /* See comments in __d_alloc on the terminating NUL character */ 48 - return qn->name[i] == '\0' ? 0 : 1; 35 + 36 + if (qn->name[i] != qd->name[i]) { 37 + *matched = i; 38 + return qn->name[i] > qd->name[i] ? 1 : -1; 39 + } 40 + 41 + ++i; 42 + goto loop; 49 43 } 50 44 51 - #define nameoff_from_disk(off, sz) (le16_to_cpu(off) & ((sz) - 1)) 52 - 53 - static struct erofs_dirent *find_target_dirent(struct erofs_qstr *name, 54 - u8 *data, 55 - unsigned int dirblksize, 56 - const int ndirents) 45 + static struct erofs_dirent *find_target_dirent( 46 + struct qstr *name, 47 + u8 *data, int maxsize) 57 48 { 58 - int head, back; 49 + unsigned int ndirents, head, back; 59 50 unsigned int startprfx, endprfx; 60 51 struct erofs_dirent *const de = (struct erofs_dirent *)data; 52 + 53 + /* make sure that maxsize is valid */ 54 + BUG_ON(maxsize < sizeof(struct erofs_dirent)); 55 + 56 + ndirents = le16_to_cpu(de->nameoff) / sizeof(*de); 57 + 58 + /* corrupted dir (may be unnecessary...) */ 59 + BUG_ON(!ndirents); 61 60 62 61 head = 0; 63 62 back = ndirents - 1; 64 63 startprfx = endprfx = 0; 65 64 66 65 while (head <= back) { 67 - const int mid = head + (back - head) / 2; 68 - const int nameoff = nameoff_from_disk(de[mid].nameoff, 69 - dirblksize); 66 + unsigned int mid = head + (back - head) / 2; 67 + unsigned int nameoff = le16_to_cpu(de[mid].nameoff); 70 68 unsigned int matched = min(startprfx, endprfx); 71 - struct erofs_qstr dname = { 72 - .name = data + nameoff, 73 - .end = unlikely(mid >= ndirents - 1) ? 74 - data + dirblksize : 75 - data + nameoff_from_disk(de[mid + 1].nameoff, 76 - dirblksize) 77 - }; 69 + 70 + struct qstr dname = QSTR_INIT(data + nameoff, 71 + unlikely(mid >= ndirents - 1) ? 72 + maxsize - nameoff : 73 + le16_to_cpu(de[mid + 1].nameoff) - nameoff); 78 74 79 75 /* string comparison without already matched prefix */ 80 76 int ret = dirnamecmp(name, &dname, &matched); 81 77 82 - if (unlikely(!ret)) { 78 + if (unlikely(!ret)) 83 79 return de + mid; 84 - } else if (ret > 0) { 80 + else if (ret > 0) { 85 81 head = mid + 1; 86 82 startprfx = matched; 87 - } else { 83 + } else if (unlikely(mid < 1)) /* fix "mid" overflow */ 84 + break; 85 + else { 88 86 back = mid - 1; 89 87 endprfx = matched; 90 88 } ··· 91 93 return ERR_PTR(-ENOENT); 92 94 } 93 95 94 - static struct page *find_target_block_classic(struct inode *dir, 95 - struct erofs_qstr *name, 96 - int *_diff, 97 - int *_ndirents) 96 + static struct page *find_target_block_classic( 97 + struct inode *dir, 98 + struct qstr *name, int *_diff) 98 99 { 99 100 unsigned int startprfx, endprfx; 100 - int head, back; 101 + unsigned int head, back; 101 102 struct address_space *const mapping = dir->i_mapping; 102 103 struct page *candidate = ERR_PTR(-ENOENT); 103 104 ··· 105 108 back = inode_datablocks(dir) - 1; 106 109 107 110 while (head <= back) { 108 - const int mid = head + (back - head) / 2; 111 + unsigned int mid = head + (back - head) / 2; 109 112 struct page *page = read_mapping_page(mapping, mid, NULL); 110 113 111 - if (!IS_ERR(page)) { 112 - struct erofs_dirent *de = kmap_atomic(page); 113 - const int nameoff = nameoff_from_disk(de->nameoff, 114 - EROFS_BLKSIZ); 115 - const int ndirents = nameoff / sizeof(*de); 114 + if (IS_ERR(page)) { 115 + exact_out: 116 + if (!IS_ERR(candidate)) /* valid candidate */ 117 + put_page(candidate); 118 + return page; 119 + } else { 116 120 int diff; 117 - unsigned int matched; 118 - struct erofs_qstr dname; 121 + unsigned int ndirents, matched; 122 + struct qstr dname; 123 + struct erofs_dirent *de = kmap_atomic(page); 124 + unsigned int nameoff = le16_to_cpu(de->nameoff); 119 125 120 - if (unlikely(!ndirents)) { 121 - DBG_BUGON(1); 122 - put_page(page); 123 - page = ERR_PTR(-EIO); 124 - goto out; 125 - } 126 + ndirents = nameoff / sizeof(*de); 127 + 128 + /* corrupted dir (should have one entry at least) */ 129 + BUG_ON(!ndirents || nameoff > PAGE_SIZE); 126 130 127 131 matched = min(startprfx, endprfx); 128 132 129 133 dname.name = (u8 *)de + nameoff; 130 - if (ndirents == 1) 131 - dname.end = (u8 *)de + EROFS_BLKSIZ; 132 - else 133 - dname.end = (u8 *)de + 134 - nameoff_from_disk(de[1].nameoff, 135 - EROFS_BLKSIZ); 134 + dname.len = ndirents == 1 ? 135 + /* since the rest of the last page is 0 */ 136 + EROFS_BLKSIZ - nameoff 137 + : le16_to_cpu(de[1].nameoff) - nameoff; 136 138 137 139 /* string comparison without already matched prefix */ 138 140 diff = dirnamecmp(name, &dname, &matched); ··· 139 143 140 144 if (unlikely(!diff)) { 141 145 *_diff = 0; 142 - goto out; 146 + goto exact_out; 143 147 } else if (diff > 0) { 144 148 head = mid + 1; 145 149 startprfx = matched; ··· 147 151 if (likely(!IS_ERR(candidate))) 148 152 put_page(candidate); 149 153 candidate = page; 150 - *_ndirents = ndirents; 151 154 } else { 152 155 put_page(page); 156 + 157 + if (unlikely(mid < 1)) /* fix "mid" overflow */ 158 + break; 153 159 154 160 back = mid - 1; 155 161 endprfx = matched; 156 162 } 157 - continue; 158 163 } 159 - out: /* free if the candidate is valid */ 160 - if (!IS_ERR(candidate)) 161 - put_page(candidate); 162 - return page; 163 164 } 164 165 *_diff = 1; 165 166 return candidate; 166 167 } 167 168 168 169 int erofs_namei(struct inode *dir, 169 - struct qstr *name, 170 - erofs_nid_t *nid, unsigned int *d_type) 170 + struct qstr *name, 171 + erofs_nid_t *nid, unsigned int *d_type) 171 172 { 172 - int diff, ndirents; 173 + int diff; 173 174 struct page *page; 174 175 u8 *data; 175 176 struct erofs_dirent *de; 176 - struct erofs_qstr qn; 177 177 178 178 if (unlikely(!dir->i_size)) 179 179 return -ENOENT; 180 180 181 - qn.name = name->name; 182 - qn.end = name->name + name->len; 183 - 184 181 diff = 1; 185 - page = find_target_block_classic(dir, &qn, &diff, &ndirents); 182 + page = find_target_block_classic(dir, name, &diff); 186 183 187 184 if (unlikely(IS_ERR(page))) 188 185 return PTR_ERR(page); ··· 184 195 /* the target page has been mapped */ 185 196 de = likely(diff) ? 186 197 /* since the rest of the last page is 0 */ 187 - find_target_dirent(&qn, data, EROFS_BLKSIZ, ndirents) : 198 + find_target_dirent(name, data, EROFS_BLKSIZ) : 188 199 (struct erofs_dirent *)data; 189 200 190 201 if (likely(!IS_ERR(de))) {