btrfs-progs: use ftw() unstead of system("du")

size_sourcedir() uses shockingly bad code to try and estimate the size
of the files and directories in a subtree.

- Its use of snprintf(), strcat(), and sscanf() with arbitrarily small
  on-stack buffers manages to overflow the stack a few times when given
  long file names.

  $ BIG=$(perl -e 'print "a" x 200')
  $ mkdir -p /tmp/$BIG/$BIG/$BIG/$BIG/$BIG
  $ mkfs.btrfs /tmp/img -r /tmp/$BIG/$BIG/$BIG/$BIG/$BIG
  *** stack smashing detected ***: mkfs.btrfs terminated

- It passes raw paths to system() allowing interpreting file names as
  shell control characters.

  $ mkfs.btrfs /tmp/img -r /tmp/spacey\ dir/
  du: cannot access `/tmp/spacey': No such file or directory
  du: cannot access `dir/': No such file or directory

- It redirects du output to "temp_file" in the current directory,
  allowing overwriting of files through symlinks.

  $ echo hi > target
  $ ln -s target temp_file
  $ mkfs.btrfs /tmp/img -r /tmp/somedir/
  $ cat target
  3	/tmp/somedir/

This fixes the worst problems while maintaining -r functionality by
tearing out the system() code and using ftw() to walk the source tree
and sum up st.st_size.

Signed-off-by: Zach Brown <zab@redhat.com>
master
Zach Brown 2013-01-17 15:23:10 -08:00
parent 968efc6f98
commit 9e4ad99099
2 changed files with 36 additions and 19 deletions

View File

@ -202,6 +202,16 @@ static inline long IS_ERR(const void *ptr)
#define max_t(type,x,y) \
({ type __x = (x); type __y = (y); __x > __y ? __x: __y; })
/*
* This looks more complex than it should be. But we need to
* get the type for the ~ right in round_down (it needs to be
* as wide as the result!), and we want to evaluate the macro
* arguments just once each.
*/
#define __round_mask(x, y) ((__typeof__(x))((y)-1))
#define round_up(x, y) ((((x)-1) | __round_mask(x, y))+1)
#define round_down(x, y) ((x) & ~__round_mask(x, y))
/*
* printk
*/

45
mkfs.c
View File

@ -40,6 +40,8 @@
#include <ctype.h>
#include <attr/xattr.h>
#include <blkid/blkid.h>
#include <ftw.h>
#include "kerncompat.h"
#include "ctree.h"
#include "disk-io.h"
#include "volumes.h"
@ -1097,16 +1099,30 @@ fail:
return -1;
}
/*
* This ignores symlinks with unreadable targets and subdirs that can't
* be read. It's a best-effort to give a rough estimate of the size of
* a subdir. It doesn't guarantee that prepopulating btrfs from this
* tree won't still run out of space.
*
* The rounding up to 4096 is questionable. Previous code used du -B 4096.
*/
static u64 global_total_size;
static int ftw_add_entry_size(const char *fpath, const struct stat *st,
int type)
{
if (type == FTW_F || type == FTW_D)
global_total_size += round_up(st->st_size, 4096);
return 0;
}
static u64 size_sourcedir(char *dir_name, u64 sectorsize,
u64 *num_of_meta_chunks_ret, u64 *size_of_data_ret)
{
u64 dir_size = 0;
u64 total_size = 0;
int ret;
char command[1024];
char path[512];
char *file_name = "temp_file";
FILE *file;
u64 default_chunk_size = 8 * 1024 * 1024; /* 8MB */
u64 allocated_meta_size = 8 * 1024 * 1024; /* 8MB */
u64 allocated_total_size = 20 * 1024 * 1024; /* 20MB */
@ -1114,23 +1130,14 @@ static u64 size_sourcedir(char *dir_name, u64 sectorsize,
u64 num_of_allocated_meta_chunks =
allocated_meta_size / default_chunk_size;
ret = sprintf(command, "du -B 4096 -s ");
global_total_size = 0;
ret = ftw(dir_name, ftw_add_entry_size, 10);
dir_size = global_total_size;
if (ret < 0) {
fprintf(stderr, "error executing sprintf for du command\n");
return -1;
fprintf(stderr, "ftw subdir walk of '%s' failed: %s\n",
dir_name, strerror(errno));
exit(1);
}
strcat(command, dir_name);
strcat(command, " > ");
strcat(command, file_name);
ret = system(command);
file = fopen(file_name, "r");
ret = fscanf(file, "%lld %s\n", &dir_size, path);
fclose(file);
remove(file_name);
dir_size *= sectorsize;
*size_of_data_ret = dir_size;
num_of_meta_chunks = (dir_size / 2) / default_chunk_size;
if (((dir_size / 2) % default_chunk_size) != 0)