Re: [PATCH] staging: erofs: keep corrupted fs from crashing kernel in erofs_namei()

2019-01-30 Thread Gao Xiang
Hi Dan,

Thanks for your kindly review.

On 2019/1/30 22:45, Dan Carpenter wrote:
> On Tue, Jan 29, 2019 at 11:55:40PM +0800, Gao Xiang wrote:
>> +static struct page *find_target_block_classic(struct inode *dir,
>> +  struct erofs_qstr *name,
>> +  int *_diff,
>> +  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,33 +108,34 @@ static struct page *find_target_block_classic(
>>  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)) {
> 
> It's almost always better to do failure handling instead of success
> handing because it lets you pull everything in one indent level.  You'd
> need to move a bunch of the declarations around.

I just want to leave definition and the initial assignment in one line...
>>  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);

or I have to 
unsigned int mid = head + (back - head) / 2;
const int mid = head + (back - head) / 2;
struct page *page = read_mapping_page(mapping, mid, NULL);
struct erofs_dirent *de;
...
int ndirents;

if (IS_ERR(page)) {
...
}

de = kmap_atomic(page);
...
ndirents = nameoff / sizeof(*de);

which takes extra lines...

> 
>   if (IS_ERR(page))
>   goto out;
> 
> But really the out label is not part of the loop so you could move it
> to the bottom of the function...

It seems that the out label is the part of loop...


> 
>>  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);
>> +put_page(page);
>> +page = ERR_PTR(-EIO);
>> +goto out;
> 
> We need to kunmap_atomic(de) on this path.

Thanks, will fix in the next version...

> 
>> +}
>>  
>>  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);
>> @@ -139,7 +143,7 @@ static struct page *find_target_block_classic(
>>  
>>  if (unlikely(!diff)) {
>>  *_diff = 0;
>> -goto exact_out;
>> +   

Re: [PATCH] staging: erofs: keep corrupted fs from crashing kernel in erofs_namei()

2019-01-30 Thread Dan Carpenter
On Tue, Jan 29, 2019 at 11:55:40PM +0800, Gao Xiang wrote:
> +static struct page *find_target_block_classic(struct inode *dir,
> +   struct erofs_qstr *name,
> +   int *_diff,
> +   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,33 +108,34 @@ static struct page *find_target_block_classic(
>   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)) {

It's almost always better to do failure handling instead of success
handing because it lets you pull everything in one indent level.  You'd
need to move a bunch of the declarations around.

if (IS_ERR(page))
goto out;

But really the out label is not part of the loop so you could move it
to the bottom of the function...

>   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);
> + put_page(page);
> + page = ERR_PTR(-EIO);
> + goto out;

We need to kunmap_atomic(de) on this path.

> + }
>  
>   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);
> @@ -139,7 +143,7 @@ static struct page *find_target_block_classic(
>  
>   if (unlikely(!diff)) {
>   *_diff = 0;
> - goto exact_out;
> + goto out;
>   } else if (diff > 0) {
>   head = mid + 1;
>   startprfx = matched;
> @@ -147,35 +151,42 @@ static struct page *find_target_block_classic(
>   if (likely(!IS_ERR(candidate)))
^^
Not related to the this patch, but I wonder how this works.  IS_ERR()
already has an opposite unlikely() inside so I wonder which trumps the
other?

>   put_page(candidate);
>   candidate = page;
> + *_ndirents = ndirents;

regards,
dan carpenter

___
devel mailing list
de...@linuxdriverproject.org
http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel


[PATCH] staging: erofs: keep corrupted fs from crashing kernel in erofs_namei()

2019-01-29 Thread Gao Xiang
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 
---
TODO:
 - fix similar issue in readdir of dir.c with another patch.

 drivers/staging/erofs/namei.c | 167 ++
 1 file changed, 89 insertions(+), 78 deletions(-)

diff --git a/drivers/staging/erofs/namei.c b/drivers/staging/erofs/namei.c
index 5596c52e246d..a1300c420e63 100644
--- a/drivers/staging/erofs/namei.c
+++ b/drivers/staging/erofs/namei.c
@@ -15,74 +15,76 @@
 
 #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;
+   unsigned int i = *matched;
+
+   /*
+* 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;
}
-   return (qn->len > qd->len);
+   ++i;
}
-
-   if (qn->name[i] != qd->name[i]) {
-   *matched = i;
-   return qn->name[i] > qd->name[i] ? 1 : -1;
-   }
-
-   ++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;
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 >=