Skip to content

Commit d4104c5

Browse files
Gao Xianggregkh
authored andcommitted
staging: erofs: keep corrupted fs from crashing kernel in erofs_namei()
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: d72d1ce ("staging: erofs: add namei functions") Cc: <stable@vger.kernel.org> # 4.19+ Suggested-by: Al Viro <viro@ZenIV.linux.org.uk> Signed-off-by: Gao Xiang <gaoxiang25@huawei.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent 49230b4 commit d4104c5

File tree

1 file changed

+89
-78
lines changed

1 file changed

+89
-78
lines changed

drivers/staging/erofs/namei.c

Lines changed: 89 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -15,74 +15,76 @@
1515

1616
#include <trace/events/erofs.h>
1717

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)
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)
2127
{
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;
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;
3243
}
33-
return (qn->len > qd->len);
44+
++i;
3445
}
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;
46+
*matched = i;
47+
/* See comments in __d_alloc on the terminating NUL character */
48+
return qn->name[i] == '\0' ? 0 : 1;
4349
}
4450

45-
static struct erofs_dirent *find_target_dirent(
46-
struct qstr *name,
47-
u8 *data, int maxsize)
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)
4857
{
49-
unsigned int ndirents, head, back;
58+
int head, back;
5059
unsigned int startprfx, endprfx;
5160
struct erofs_dirent *const de = (struct erofs_dirent *)data;
5261

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);
60-
6162
head = 0;
6263
back = ndirents - 1;
6364
startprfx = endprfx = 0;
6465

6566
while (head <= back) {
66-
unsigned int mid = head + (back - head) / 2;
67-
unsigned int nameoff = le16_to_cpu(de[mid].nameoff);
67+
const int mid = head + (back - head) / 2;
68+
const int nameoff = nameoff_from_disk(de[mid].nameoff,
69+
dirblksize);
6870
unsigned int matched = min(startprfx, endprfx);
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);
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+
};
7478

7579
/* string comparison without already matched prefix */
7680
int ret = dirnamecmp(name, &dname, &matched);
7781

78-
if (unlikely(!ret))
82+
if (unlikely(!ret)) {
7983
return de + mid;
80-
else if (ret > 0) {
84+
} else if (ret > 0) {
8185
head = mid + 1;
8286
startprfx = matched;
83-
} else if (unlikely(mid < 1)) /* fix "mid" overflow */
84-
break;
85-
else {
87+
} else {
8688
back = mid - 1;
8789
endprfx = matched;
8890
}
@@ -91,12 +93,13 @@ static struct erofs_dirent *find_target_dirent(
9193
return ERR_PTR(-ENOENT);
9294
}
9395

94-
static struct page *find_target_block_classic(
95-
struct inode *dir,
96-
struct qstr *name, int *_diff)
96+
static struct page *find_target_block_classic(struct inode *dir,
97+
struct erofs_qstr *name,
98+
int *_diff,
99+
int *_ndirents)
97100
{
98101
unsigned int startprfx, endprfx;
99-
unsigned int head, back;
102+
int head, back;
100103
struct address_space *const mapping = dir->i_mapping;
101104
struct page *candidate = ERR_PTR(-ENOENT);
102105

@@ -105,77 +108,85 @@ static struct page *find_target_block_classic(
105108
back = inode_datablocks(dir) - 1;
106109

107110
while (head <= back) {
108-
unsigned int mid = head + (back - head) / 2;
111+
const int mid = head + (back - head) / 2;
109112
struct page *page = read_mapping_page(mapping, mid, NULL);
110113

111-
if (IS_ERR(page)) {
112-
exact_out:
113-
if (!IS_ERR(candidate)) /* valid candidate */
114-
put_page(candidate);
115-
return page;
116-
} else {
117-
int diff;
118-
unsigned int ndirents, matched;
119-
struct qstr dname;
114+
if (!IS_ERR(page)) {
120115
struct erofs_dirent *de = kmap_atomic(page);
121-
unsigned int nameoff = le16_to_cpu(de->nameoff);
122-
123-
ndirents = nameoff / sizeof(*de);
116+
const int nameoff = nameoff_from_disk(de->nameoff,
117+
EROFS_BLKSIZ);
118+
const int ndirents = nameoff / sizeof(*de);
119+
int diff;
120+
unsigned int matched;
121+
struct erofs_qstr dname;
124122

125-
/* corrupted dir (should have one entry at least) */
126-
BUG_ON(!ndirents || nameoff > PAGE_SIZE);
123+
if (unlikely(!ndirents)) {
124+
DBG_BUGON(1);
125+
put_page(page);
126+
page = ERR_PTR(-EIO);
127+
goto out;
128+
}
127129

128130
matched = min(startprfx, endprfx);
129131

130132
dname.name = (u8 *)de + nameoff;
131-
dname.len = ndirents == 1 ?
132-
/* since the rest of the last page is 0 */
133-
EROFS_BLKSIZ - nameoff
134-
: le16_to_cpu(de[1].nameoff) - nameoff;
133+
if (ndirents == 1)
134+
dname.end = (u8 *)de + EROFS_BLKSIZ;
135+
else
136+
dname.end = (u8 *)de +
137+
nameoff_from_disk(de[1].nameoff,
138+
EROFS_BLKSIZ);
135139

136140
/* string comparison without already matched prefix */
137141
diff = dirnamecmp(name, &dname, &matched);
138142
kunmap_atomic(de);
139143

140144
if (unlikely(!diff)) {
141145
*_diff = 0;
142-
goto exact_out;
146+
goto out;
143147
} else if (diff > 0) {
144148
head = mid + 1;
145149
startprfx = matched;
146150

147151
if (likely(!IS_ERR(candidate)))
148152
put_page(candidate);
149153
candidate = page;
154+
*_ndirents = ndirents;
150155
} else {
151156
put_page(page);
152157

153-
if (unlikely(mid < 1)) /* fix "mid" overflow */
154-
break;
155-
156158
back = mid - 1;
157159
endprfx = matched;
158160
}
161+
continue;
159162
}
163+
out: /* free if the candidate is valid */
164+
if (!IS_ERR(candidate))
165+
put_page(candidate);
166+
return page;
160167
}
161168
*_diff = 1;
162169
return candidate;
163170
}
164171

165172
int erofs_namei(struct inode *dir,
166-
struct qstr *name,
167-
erofs_nid_t *nid, unsigned int *d_type)
173+
struct qstr *name,
174+
erofs_nid_t *nid, unsigned int *d_type)
168175
{
169-
int diff;
176+
int diff, ndirents;
170177
struct page *page;
171178
u8 *data;
172179
struct erofs_dirent *de;
180+
struct erofs_qstr qn;
173181

174182
if (unlikely(!dir->i_size))
175183
return -ENOENT;
176184

185+
qn.name = name->name;
186+
qn.end = name->name + name->len;
187+
177188
diff = 1;
178-
page = find_target_block_classic(dir, name, &diff);
189+
page = find_target_block_classic(dir, &qn, &diff, &ndirents);
179190

180191
if (unlikely(IS_ERR(page)))
181192
return PTR_ERR(page);
@@ -184,7 +195,7 @@ int erofs_namei(struct inode *dir,
184195
/* the target page has been mapped */
185196
de = likely(diff) ?
186197
/* since the rest of the last page is 0 */
187-
find_target_dirent(name, data, EROFS_BLKSIZ) :
198+
find_target_dirent(&qn, data, EROFS_BLKSIZ, ndirents) :
188199
(struct erofs_dirent *)data;
189200

190201
if (likely(!IS_ERR(de))) {

0 commit comments

Comments
 (0)