Skip to content
This repository was archived by the owner on Jun 9, 2020. It is now read-only.

Commit 46437cb

Browse files
committed
Fix open() to avoid use-after-free
open() internally uses the "struct file" array. The array is expanded when necessary with realloc(). realloc() may first free the old memory region and then allocate a new region. However, a pointer into the array might be still in use when the array is expanded, causing use-after-free. This patch introduces a two-level page-table-like data structure to avoid reallocating the "struct file" array.
1 parent e487d0b commit 46437cb

2 files changed

Lines changed: 64 additions & 36 deletions

File tree

include/noah.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ struct task {
8181
struct fdtable {
8282
int start; // First fd number of this table
8383
int size; // Current table size expressed in number of bits
84-
struct file *files;
84+
struct file **files;
8585
uint64_t *open_fds;
8686
uint64_t *cloexec_fds;
8787
};

src/fs/fs.c

Lines changed: 63 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -84,36 +84,62 @@ struct file_operations {
8484
};
8585

8686
static inline bool in_userfd(int fd);
87+
static const int user_fdtable_initsize = 64;
8788
static const int vkern_fdtable_maxsize = 64;
89+
static const int fdtable_alloc_unit = 64; // must be a multiple of 64
8890

8991
static inline void set_fdbit(struct fdtable *table, uint64_t *fdbits, int fd);
9092
static inline void clear_fdbit(struct fdtable *table, uint64_t *fdbits, int fd);
9193

94+
static inline int div_ceil(int x, int y) { return (x + y - 1) / y; }
95+
96+
int
97+
alloc_fdtable(struct fdtable *fdtable, int newsize)
98+
{
99+
newsize = div_ceil(newsize, fdtable_alloc_unit) * fdtable_alloc_unit;
100+
int oldsize = fdtable->size;
101+
if (newsize <= oldsize)
102+
return 0;
103+
104+
int newunit = newsize / fdtable_alloc_unit;
105+
int oldunit = oldsize / fdtable_alloc_unit;
106+
fdtable->files = realloc(fdtable->files, sizeof(struct file *) * newunit);
107+
if (fdtable->files == NULL)
108+
return -LINUX_ENOMEM;
109+
for (int i = oldunit; i < newunit; i++) {
110+
fdtable->files[i] = calloc(fdtable_alloc_unit, sizeof(struct file));
111+
if (fdtable->files[i] == NULL)
112+
return -LINUX_ENOMEM;
113+
}
114+
115+
int newfdslen = newsize / 8;
116+
fdtable->open_fds = realloc(fdtable->open_fds, newfdslen);
117+
if (fdtable->open_fds == NULL)
118+
return -LINUX_ENOMEM;
119+
fdtable->cloexec_fds = realloc(fdtable->cloexec_fds, newfdslen);
120+
if (fdtable->cloexec_fds == NULL)
121+
return -LINUX_ENOMEM;
122+
int offset = oldsize / 8;
123+
int size = newfdslen - offset;
124+
bzero(fdtable->open_fds + offset, size);
125+
bzero(fdtable->cloexec_fds + offset, size);
126+
127+
fdtable->size = newsize;
128+
return 0;
129+
}
130+
92131
void
93132
init_fileinfo(int rootfd)
94133
{
95134
struct rlimit limit;
96135
struct fileinfo *fileinfo = &proc.fileinfo;
97136

98137
getrlimit(RLIMIT_NOFILE, &limit);
99-
fileinfo->vkern_fdtable = (struct fdtable) {
100-
.start = limit.rlim_cur - 64,
101-
.size = 64,
102-
.files = malloc(sizeof(struct file) * 64),
103-
.open_fds = malloc(sizeof(uint64_t)),
104-
.cloexec_fds = malloc(sizeof(uint64_t))
105-
};
106-
fileinfo->vkern_fdtable.open_fds[0] = 0;
107-
fileinfo->vkern_fdtable.cloexec_fds[0] = 0;
108-
fileinfo->fdtable = (struct fdtable) {
109-
.start = 0,
110-
.size = vkern_fdtable_maxsize,
111-
.files = malloc(sizeof(struct file) * vkern_fdtable_maxsize),
112-
.open_fds = malloc(sizeof(uint64_t)),
113-
.cloexec_fds = malloc(sizeof(uint64_t))
114-
};
115-
fileinfo->fdtable.open_fds[0] = 0;
116-
fileinfo->fdtable.cloexec_fds[0] = 0;
138+
fileinfo->vkern_fdtable = (struct fdtable) { 0, 0, NULL, NULL, NULL };
139+
fileinfo->vkern_fdtable.start = limit.rlim_cur - vkern_fdtable_maxsize;
140+
alloc_fdtable(&fileinfo->vkern_fdtable, vkern_fdtable_maxsize);
141+
fileinfo->fdtable = (struct fdtable) { 0, 0, NULL, NULL, NULL };
142+
alloc_fdtable(&fileinfo->fdtable, user_fdtable_initsize);
117143

118144
for (int i = 0; i < (int) limit.rlim_cur; i++) {
119145
if (i == rootfd) {
@@ -500,7 +526,8 @@ alloc_file(struct fdtable *table, int fd)
500526
darwinfs_fchmod,
501527
};
502528

503-
struct file *file = table->files + (fd - table->start);
529+
int offset = fd - table->start;
530+
struct file *file = &table->files[offset / fdtable_alloc_unit][offset % fdtable_alloc_unit];
504531
file->ops = &ops;
505532
file->fd = fd;
506533
}
@@ -518,16 +545,9 @@ register_fd(int fd, bool is_cloexec)
518545
}
519546
struct fdtable *fdtable = &proc.fileinfo.fdtable;
520547
if (proc.fileinfo.fdtable.size <= fd) {
521-
// Expand table
522-
int new_size = roundup(fd, sizeof(uint64_t));
523-
size_t old_nunits = proc.fileinfo.fdtable.size / 64;
524-
size_t new_nunits = new_size / 64;
525-
fdtable->files = realloc(fdtable->files, new_size * sizeof(struct file));
526-
fdtable->open_fds = realloc(fdtable->open_fds, sizeof(uint64_t) * new_nunits);
527-
fdtable->cloexec_fds = realloc(fdtable->cloexec_fds, sizeof(uint64_t) * new_nunits);
528-
bzero(fdtable->open_fds + old_nunits, (new_nunits - old_nunits) * sizeof(uint64_t));
529-
bzero(fdtable->cloexec_fds + old_nunits, (new_nunits - old_nunits) * sizeof(uint64_t));
530-
fdtable->size = new_size;
548+
int err = alloc_fdtable(fdtable, fd + 1);
549+
if (err < 0)
550+
return err;
531551
}
532552
set_fdbit(fdtable, fdtable->open_fds, fd);
533553
if (is_cloexec) {
@@ -570,18 +590,25 @@ vkern_dup_fd(int fd, bool is_cloexec)
570590
}
571591

572592
struct file *
573-
get_file(int fd)
593+
do_get_file(struct fdtable *table, int fd)
574594
{
575-
if (fd < 0 || fd >= proc.fileinfo.fdtable.size) {
595+
if (!test_fdbit(table, table->open_fds, fd)) {
576596
return NULL;
577597
}
598+
int offset = fd - table->start;
599+
return &table->files[offset / fdtable_alloc_unit][offset % fdtable_alloc_unit];
600+
}
578601

602+
struct file *
603+
get_file(int fd)
604+
{
579605
struct file *ret = NULL;
606+
struct fdtable *table = &proc.fileinfo.fdtable;
580607
pthread_rwlock_rdlock(&proc.fileinfo.fdtable_lock);
581-
if (!test_fdbit(&proc.fileinfo.fdtable, proc.fileinfo.fdtable.open_fds, fd)) {
608+
if (fd < 0 || fd >= table->size) {
582609
goto out;
583610
}
584-
ret = &proc.fileinfo.fdtable.files[fd - proc.fileinfo.fdtable.start];
611+
ret = do_get_file(table, fd);
585612

586613
out:
587614
pthread_rwlock_unlock(&proc.fileinfo.fdtable_lock);
@@ -1161,8 +1188,9 @@ do_close(struct fdtable *table, int fd)
11611188
if (!test_fdbit(table, table->open_fds, fd)) {
11621189
return -LINUX_EBADF;
11631190
}
1164-
struct file *file = &table->files[fd - table->start];
1165-
assert(file);
1191+
struct file *file = do_get_file(table, fd);
1192+
if (file == NULL)
1193+
return -LINUX_EBADF;
11661194
int n = file->ops->close(file);
11671195
clear_fdbit(table, table->open_fds, fd);
11681196
clear_fdbit(table, table->cloexec_fds, fd);

0 commit comments

Comments
 (0)