Skip to content

Commit

Permalink
prevent locking fd table while holding a mutex
Browse files Browse the repository at this point in the history
uvwasi_path_rename(), uvwasi_path_link(), uvwasi_path_open(), and
uvwasi_fd_renumber() operate on multiple file descriptors.
uvwasi_fd_renumber() has been updated prior to this commit, and
is not relevant here. The other three functions would perform
the following locking operations:

- lock the file table
- acquire a file descriptor mutex
- unlock the file table
- unlock the file table again
- acquire another file descriptor mutex
- unlock the file table
- unlock the two mutexes

Attempting to acquire the second mutex introduced the possibility
of deadlock because another thread could attempt to acquire the first
mutex while holding the file table lock.

This commit ensures that multiple mutexes are either:
- acquired in a single lock of the file table
- or, only acquired after releasing previously held mutexes

Fixes: #89
  • Loading branch information
cjihrig committed Jan 22, 2020
1 parent ea73af5 commit 611f84f
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 69 deletions.
9 changes: 8 additions & 1 deletion include/fd_table.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,24 @@ uvwasi_errno_t uvwasi_fd_table_insert_preopen(struct uvwasi_s* uvwasi,
const uv_file fd,
const char* path,
const char* real_path);
uvwasi_errno_t uvwasi_fd_table_get(const struct uvwasi_fd_table_t* table,
uvwasi_errno_t uvwasi_fd_table_get(struct uvwasi_fd_table_t* table,
const uvwasi_fd_t id,
struct uvwasi_fd_wrap_t** wrap,
uvwasi_rights_t rights_base,
uvwasi_rights_t rights_inheriting);
uvwasi_errno_t uvwasi_fd_table_get_nolock(struct uvwasi_fd_table_t* table,
const uvwasi_fd_t id,
struct uvwasi_fd_wrap_t** wrap,
uvwasi_rights_t rights_base,
uvwasi_rights_t rights_inheriting);
uvwasi_errno_t uvwasi_fd_table_remove(struct uvwasi_s* uvwasi,
struct uvwasi_fd_table_t* table,
const uvwasi_fd_t id);
uvwasi_errno_t uvwasi_fd_table_renumber(struct uvwasi_s* uvwasi,
struct uvwasi_fd_table_t* table,
const uvwasi_fd_t dst,
const uvwasi_fd_t src);
uvwasi_errno_t uvwasi_fd_table_lock(struct uvwasi_fd_table_t* table);
uvwasi_errno_t uvwasi_fd_table_unlock(struct uvwasi_fd_table_t* table);

#endif /* __UVWASI_FD_TABLE_H__ */
67 changes: 49 additions & 18 deletions src/fd_table.c
Original file line number Diff line number Diff line change
Expand Up @@ -252,44 +252,57 @@ uvwasi_errno_t uvwasi_fd_table_insert_preopen(uvwasi_t* uvwasi,
}


uvwasi_errno_t uvwasi_fd_table_get(const struct uvwasi_fd_table_t* table,
uvwasi_errno_t uvwasi_fd_table_get(struct uvwasi_fd_table_t* table,
const uvwasi_fd_t id,
struct uvwasi_fd_wrap_t** wrap,
uvwasi_rights_t rights_base,
uvwasi_rights_t rights_inheriting) {
struct uvwasi_fd_wrap_t* entry;
uvwasi_errno_t err;

if (table == NULL || wrap == NULL)
if (table == NULL)
return UVWASI_EINVAL;

uv_rwlock_rdlock((uv_rwlock_t *)&table->rwlock);
uv_rwlock_wrlock(&table->rwlock);
err = uvwasi_fd_table_get_nolock(table,
id,
wrap,
rights_base,
rights_inheriting);
uv_rwlock_wrunlock(&table->rwlock);
return err;
}

if (id >= table->size) {
err = UVWASI_EBADF;
goto exit;
}

/* uvwasi_fd_table_get_nolock() retrieves a file descriptor and locks its mutex,
but does not lock the file descriptor table like uvwasi_fd_table_get() does.
*/
uvwasi_errno_t uvwasi_fd_table_get_nolock(struct uvwasi_fd_table_t* table,
const uvwasi_fd_t id,
struct uvwasi_fd_wrap_t** wrap,
uvwasi_rights_t rights_base,
uvwasi_rights_t rights_inheriting) {
struct uvwasi_fd_wrap_t* entry;

if (table == NULL || wrap == NULL)
return UVWASI_EINVAL;

if (id >= table->size)
return UVWASI_EBADF;

entry = table->fds[id];

if (entry == NULL || entry->id != id) {
err = UVWASI_EBADF;
goto exit;
}
if (entry == NULL || entry->id != id)
return UVWASI_EBADF;

/* Validate that the fd has the necessary rights. */
if ((~entry->rights_base & rights_base) != 0 ||
(~entry->rights_inheriting & rights_inheriting) != 0) {
err = UVWASI_ENOTCAPABLE;
goto exit;
return UVWASI_ENOTCAPABLE;
}

uv_mutex_lock(&entry->mutex);
*wrap = entry;
err = UVWASI_ESUCCESS;
exit:
uv_rwlock_rdunlock((uv_rwlock_t *)&table->rwlock);
return err;
return UVWASI_ESUCCESS;
}


Expand Down Expand Up @@ -389,3 +402,21 @@ uvwasi_errno_t uvwasi_fd_table_renumber(struct uvwasi_s* uvwasi,
uv_rwlock_wrunlock(&table->rwlock);
return err;
}


uvwasi_errno_t uvwasi_fd_table_lock(struct uvwasi_fd_table_t* table) {
if (table == NULL)
return UVWASI_EINVAL;

uv_rwlock_wrlock(&table->rwlock);
return UVWASI_ESUCCESS;
}


uvwasi_errno_t uvwasi_fd_table_unlock(struct uvwasi_fd_table_t* table) {
if (table == NULL)
return UVWASI_EINVAL;

uv_rwlock_wrunlock(&table->rwlock);
return UVWASI_ESUCCESS;
}
106 changes: 56 additions & 50 deletions src/uvwasi.c
Original file line number Diff line number Diff line change
Expand Up @@ -1763,37 +1763,41 @@ uvwasi_errno_t uvwasi_path_link(uvwasi_t* uvwasi,
if (uvwasi == NULL || old_path == NULL || new_path == NULL)
return UVWASI_EINVAL;

if (old_fd == new_fd) {
err = uvwasi_fd_table_get(&uvwasi->fds,
old_fd,
&old_wrap,
UVWASI_RIGHT_PATH_LINK_SOURCE |
UVWASI_RIGHT_PATH_LINK_TARGET,
0);
if (err != UVWASI_ESUCCESS)
return err;
uvwasi_fd_table_lock(&uvwasi->fds);

if (old_fd == new_fd) {
err = uvwasi_fd_table_get_nolock(&uvwasi->fds,
old_fd,
&old_wrap,
UVWASI_RIGHT_PATH_LINK_SOURCE |
UVWASI_RIGHT_PATH_LINK_TARGET,
0);
new_wrap = old_wrap;
} else {
err = uvwasi_fd_table_get(&uvwasi->fds,
old_fd,
&old_wrap,
UVWASI_RIGHT_PATH_LINK_SOURCE,
0);
if (err != UVWASI_ESUCCESS)
return err;

err = uvwasi_fd_table_get(&uvwasi->fds,
new_fd,
&new_wrap,
UVWASI_RIGHT_PATH_LINK_TARGET,
0);
err = uvwasi_fd_table_get_nolock(&uvwasi->fds,
old_fd,
&old_wrap,
UVWASI_RIGHT_PATH_LINK_SOURCE,
0);
if (err != UVWASI_ESUCCESS) {
uv_mutex_unlock(&old_wrap->mutex);
uvwasi_fd_table_unlock(&uvwasi->fds);
return err;
}

err = uvwasi_fd_table_get_nolock(&uvwasi->fds,
new_fd,
&new_wrap,
UVWASI_RIGHT_PATH_LINK_TARGET,
0);
if (err != UVWASI_ESUCCESS)
uv_mutex_unlock(&old_wrap->mutex);
}

uvwasi_fd_table_unlock(&uvwasi->fds);

if (err != UVWASI_ESUCCESS)
return err;

err = uvwasi__resolve_path(uvwasi,
old_wrap,
old_path,
Expand Down Expand Up @@ -1922,12 +1926,11 @@ uvwasi_errno_t uvwasi_path_open(uvwasi_t* uvwasi,
}

r = uv_fs_open(NULL, &req, resolved_path, flags, 0666, NULL);
uv_mutex_unlock(&dirfd_wrap->mutex);
uv_fs_req_cleanup(&req);

if (r < 0) {
uv_mutex_unlock(&dirfd_wrap->mutex);
if (r < 0)
return uvwasi__translate_uv_error(r);
}

/* Not all platforms support UV_FS_O_DIRECTORY, so get the file type and check
it here. */
Expand Down Expand Up @@ -1960,11 +1963,9 @@ uvwasi_errno_t uvwasi_path_open(uvwasi_t* uvwasi,

*fd = wrap->id;
uv_mutex_unlock(&wrap->mutex);
uv_mutex_unlock(&dirfd_wrap->mutex);
return UVWASI_ESUCCESS;

close_file_and_error_exit:
uv_mutex_unlock(&dirfd_wrap->mutex);
uv_fs_close(NULL, &req, r, NULL);
uv_fs_req_cleanup(&req);
return err;
Expand Down Expand Up @@ -2079,36 +2080,41 @@ uvwasi_errno_t uvwasi_path_rename(uvwasi_t* uvwasi,
if (uvwasi == NULL || old_path == NULL || new_path == NULL)
return UVWASI_EINVAL;

uvwasi_fd_table_lock(&uvwasi->fds);

if (old_fd == new_fd) {
err = uvwasi_fd_table_get(&uvwasi->fds,
old_fd,
&old_wrap,
UVWASI_RIGHT_PATH_RENAME_SOURCE |
UVWASI_RIGHT_PATH_RENAME_TARGET,
0);
if (err != UVWASI_ESUCCESS)
return err;
err = uvwasi_fd_table_get_nolock(&uvwasi->fds,
old_fd,
&old_wrap,
UVWASI_RIGHT_PATH_RENAME_SOURCE |
UVWASI_RIGHT_PATH_RENAME_TARGET,
0);
new_wrap = old_wrap;
} else {
err = uvwasi_fd_table_get(&uvwasi->fds,
old_fd,
&old_wrap,
UVWASI_RIGHT_PATH_RENAME_SOURCE,
0);
if (err != UVWASI_ESUCCESS)
return err;

err = uvwasi_fd_table_get(&uvwasi->fds,
new_fd,
&new_wrap,
UVWASI_RIGHT_PATH_RENAME_TARGET,
0);
err = uvwasi_fd_table_get_nolock(&uvwasi->fds,
old_fd,
&old_wrap,
UVWASI_RIGHT_PATH_RENAME_SOURCE,
0);
if (err != UVWASI_ESUCCESS) {
uv_mutex_unlock(&old_wrap->mutex);
uvwasi_fd_table_unlock(&uvwasi->fds);
return err;
}

err = uvwasi_fd_table_get_nolock(&uvwasi->fds,
new_fd,
&new_wrap,
UVWASI_RIGHT_PATH_RENAME_TARGET,
0);
if (err != UVWASI_ESUCCESS)
uv_mutex_unlock(&old_wrap->mutex);
}

uvwasi_fd_table_unlock(&uvwasi->fds);

if (err != UVWASI_ESUCCESS)
return err;

err = uvwasi__resolve_path(uvwasi,
old_wrap,
old_path,
Expand Down

0 comments on commit 611f84f

Please sign in to comment.