diff mbox

btrfs: fix unaligned access in readdir

Message ID 20180418131731.11519-1-dsterba@suse.com (mailing list archive)
State New, archived
Headers show

Commit Message

David Sterba April 18, 2018, 1:17 p.m. UTC
The last update to readdir introduced a temporary buffer to store the
emitted readdir data, but as there are file names of variable length,
there's a lot of unaligned access.

This was observed on a sparc64 machine:

  Kernel unaligned access at TPC[102f3080] btrfs_real_readdir+0x51c/0x718 [btrfs]

Fixes: 23b5ec74943 ("btrfs: fix readdir deadlock with pagefault")
CC: stable@vger.kernel.org # 4.14+
Reported-and-tested-by: René Rebe <rene@exactcode.com>
Signed-off-by: David Sterba <dsterba@suse.com>
---
 fs/btrfs/inode.c | 20 ++++++++++++--------
 1 file changed, 12 insertions(+), 8 deletions(-)

Comments

Liu Bo April 18, 2018, 5:12 p.m. UTC | #1
On Wed, Apr 18, 2018 at 6:17 AM, David Sterba <dsterba@suse.com> wrote:
> The last update to readdir introduced a temporary buffer to store the
> emitted readdir data, but as there are file names of variable length,
> there's a lot of unaligned access.
>
> This was observed on a sparc64 machine:
>
>   Kernel unaligned access at TPC[102f3080] btrfs_real_readdir+0x51c/0x718 [btrfs]
>

Reviewed-by: Liu Bo <bo.liu@linux.alibaba.com>

thanks,
liubo
> Fixes: 23b5ec74943 ("btrfs: fix readdir deadlock with pagefault")
> CC: stable@vger.kernel.org # 4.14+
> Reported-and-tested-by: René Rebe <rene@exactcode.com>
> Signed-off-by: David Sterba <dsterba@suse.com>
> ---
>  fs/btrfs/inode.c | 20 ++++++++++++--------
>  1 file changed, 12 insertions(+), 8 deletions(-)
>
> diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
> index e064c49c9a9a..d241285a0d2a 100644
> --- a/fs/btrfs/inode.c
> +++ b/fs/btrfs/inode.c
> @@ -31,6 +31,7 @@
>  #include <linux/uio.h>
>  #include <linux/magic.h>
>  #include <linux/iversion.h>
> +#include <asm/unaligned.h>
>  #include "ctree.h"
>  #include "disk-io.h"
>  #include "transaction.h"
> @@ -5905,11 +5906,13 @@ static int btrfs_filldir(void *addr, int entries, struct dir_context *ctx)
>                 struct dir_entry *entry = addr;
>                 char *name = (char *)(entry + 1);
>
> -               ctx->pos = entry->offset;
> -               if (!dir_emit(ctx, name, entry->name_len, entry->ino,
> -                             entry->type))
> +               ctx->pos = get_unaligned(&entry->offset);
> +               if (!dir_emit(ctx, name, get_unaligned(&entry->name_len),
> +                                        get_unaligned(&entry->ino),
> +                                        get_unaligned(&entry->type)))
>                         return 1;
> -               addr += sizeof(struct dir_entry) + entry->name_len;
> +               addr += sizeof(struct dir_entry) +
> +                       get_unaligned(&entry->name_len);
>                 ctx->pos++;
>         }
>         return 0;
> @@ -5999,14 +6002,15 @@ static int btrfs_real_readdir(struct file *file, struct dir_context *ctx)
>                 }
>
>                 entry = addr;
> -               entry->name_len = name_len;
> +               put_unaligned(name_len, &entry->name_len);
>                 name_ptr = (char *)(entry + 1);
>                 read_extent_buffer(leaf, name_ptr, (unsigned long)(di + 1),
>                                    name_len);
> -               entry->type = btrfs_filetype_table[btrfs_dir_type(leaf, di)];
> +               put_unaligned(btrfs_filetype_table[btrfs_dir_type(leaf, di)],
> +                               &entry->type);
>                 btrfs_dir_item_key_to_cpu(leaf, di, &location);
> -               entry->ino = location.objectid;
> -               entry->offset = found_key.offset;
> +               put_unaligned(location.objectid, &entry->ino);
> +               put_unaligned(found_key.offset, &entry->offset);
>                 entries++;
>                 addr += sizeof(struct dir_entry) + name_len;
>                 total_len += sizeof(struct dir_entry) + name_len;
> --
> 2.16.2
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index e064c49c9a9a..d241285a0d2a 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -31,6 +31,7 @@ 
 #include <linux/uio.h>
 #include <linux/magic.h>
 #include <linux/iversion.h>
+#include <asm/unaligned.h>
 #include "ctree.h"
 #include "disk-io.h"
 #include "transaction.h"
@@ -5905,11 +5906,13 @@  static int btrfs_filldir(void *addr, int entries, struct dir_context *ctx)
 		struct dir_entry *entry = addr;
 		char *name = (char *)(entry + 1);
 
-		ctx->pos = entry->offset;
-		if (!dir_emit(ctx, name, entry->name_len, entry->ino,
-			      entry->type))
+		ctx->pos = get_unaligned(&entry->offset);
+		if (!dir_emit(ctx, name, get_unaligned(&entry->name_len),
+					 get_unaligned(&entry->ino),
+					 get_unaligned(&entry->type)))
 			return 1;
-		addr += sizeof(struct dir_entry) + entry->name_len;
+		addr += sizeof(struct dir_entry) +
+			get_unaligned(&entry->name_len);
 		ctx->pos++;
 	}
 	return 0;
@@ -5999,14 +6002,15 @@  static int btrfs_real_readdir(struct file *file, struct dir_context *ctx)
 		}
 
 		entry = addr;
-		entry->name_len = name_len;
+		put_unaligned(name_len, &entry->name_len);
 		name_ptr = (char *)(entry + 1);
 		read_extent_buffer(leaf, name_ptr, (unsigned long)(di + 1),
 				   name_len);
-		entry->type = btrfs_filetype_table[btrfs_dir_type(leaf, di)];
+		put_unaligned(btrfs_filetype_table[btrfs_dir_type(leaf, di)],
+				&entry->type);
 		btrfs_dir_item_key_to_cpu(leaf, di, &location);
-		entry->ino = location.objectid;
-		entry->offset = found_key.offset;
+		put_unaligned(location.objectid, &entry->ino);
+		put_unaligned(found_key.offset, &entry->offset);
 		entries++;
 		addr += sizeof(struct dir_entry) + name_len;
 		total_len += sizeof(struct dir_entry) + name_len;