Skip to content

Commit 419d6ef

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 18f2153 commit 419d6ef

File tree

1 file changed

+97
-86
lines changed

1 file changed

+97
-86
lines changed

drivers/staging/erofs/namei.c

Lines changed: 97 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -15,74 +15,77 @@
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-
61-
head = 0;
62+
/* since the 1st dirent has been evaluated previously */
63+
head = 1;
6264
back = ndirents - 1;
6365
startprfx = endprfx = 0;
6466

6567
while (head <= back) {
66-
unsigned int mid = head + (back - head) / 2;
67-
unsigned int nameoff = le16_to_cpu(de[mid].nameoff);
68+
const int mid = head + (back - head) / 2;
69+
const int nameoff = nameoff_from_disk(de[mid].nameoff,
70+
dirblksize);
6871
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);
72+
struct erofs_qstr dname = {
73+
.name = data + nameoff,
74+
.end = unlikely(mid >= ndirents - 1) ?
75+
data + dirblksize :
76+
data + nameoff_from_disk(de[mid + 1].nameoff,
77+
dirblksize)
78+
};
7479

7580
/* string comparison without already matched prefix */
7681
int ret = dirnamecmp(name, &dname, &matched);
7782

78-
if (unlikely(!ret))
83+
if (unlikely(!ret)) {
7984
return de + mid;
80-
else if (ret > 0) {
85+
} else if (ret > 0) {
8186
head = mid + 1;
8287
startprfx = matched;
83-
} else if (unlikely(mid < 1)) /* fix "mid" overflow */
84-
break;
85-
else {
88+
} else {
8689
back = mid - 1;
8790
endprfx = matched;
8891
}
@@ -91,12 +94,12 @@ static struct erofs_dirent *find_target_dirent(
9194
return ERR_PTR(-ENOENT);
9295
}
9396

94-
static struct page *find_target_block_classic(
95-
struct inode *dir,
96-
struct qstr *name, int *_diff)
97+
static struct page *find_target_block_classic(struct inode *dir,
98+
struct erofs_qstr *name,
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,87 +108,95 @@ 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+
kunmap_atomic(de);
126+
put_page(page);
127+
page = ERR_PTR(-EIO);
128+
goto out;
129+
}
127130

128131
matched = min(startprfx, endprfx);
129132

130133
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;
134+
if (ndirents == 1)
135+
dname.end = (u8 *)de + EROFS_BLKSIZ;
136+
else
137+
dname.end = (u8 *)de +
138+
nameoff_from_disk(de[1].nameoff,
139+
EROFS_BLKSIZ);
135140

136141
/* string comparison without already matched prefix */
137142
diff = dirnamecmp(name, &dname, &matched);
138143
kunmap_atomic(de);
139144

140145
if (unlikely(!diff)) {
141-
*_diff = 0;
142-
goto exact_out;
146+
*_ndirents = 0;
147+
goto out;
143148
} else if (diff > 0) {
144149
head = mid + 1;
145150
startprfx = matched;
146151

147152
if (!IS_ERR(candidate))
148153
put_page(candidate);
149154
candidate = page;
155+
*_ndirents = ndirents;
150156
} else {
151157
put_page(page);
152158

153-
if (unlikely(mid < 1)) /* fix "mid" overflow */
154-
break;
155-
156159
back = mid - 1;
157160
endprfx = matched;
158161
}
162+
continue;
159163
}
164+
out: /* free if the candidate is valid */
165+
if (!IS_ERR(candidate))
166+
put_page(candidate);
167+
return page;
160168
}
161-
*_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 ndirents;
170177
struct page *page;
171-
u8 *data;
178+
void *data;
172179
struct erofs_dirent *de;
180+
struct erofs_qstr qn;
173181

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

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

180191
if (IS_ERR(page))
181192
return PTR_ERR(page);
182193

183194
data = kmap_atomic(page);
184195
/* the target page has been mapped */
185-
de = likely(diff) ?
186-
/* since the rest of the last page is 0 */
187-
find_target_dirent(name, data, EROFS_BLKSIZ) :
188-
(struct erofs_dirent *)data;
196+
if (ndirents)
197+
de = find_target_dirent(&qn, data, EROFS_BLKSIZ, ndirents);
198+
else
199+
de = (struct erofs_dirent *)data;
189200

190201
if (!IS_ERR(de)) {
191202
*nid = le64_to_cpu(de->nid);

0 commit comments

Comments
 (0)