9pfs: local: symlink: don't follow symlinks
The local_symlink() callback is vulnerable to symlink attacks because it calls: (1) symlink() which follows symbolic links for all path elements but the rightmost one (2) open(O_NOFOLLOW) which follows symbolic links for all path elements but the rightmost one (3) local_set_xattr()->setxattr() which follows symbolic links for all path elements (4) local_set_mapped_file_attr() which calls in turn local_fopen() and mkdir(), both functions following symbolic links for all path elements but the rightmost one This patch converts local_symlink() to rely on opendir_nofollow() and symlinkat() to fix (1), openat(O_NOFOLLOW) to fix (2), as well as local_set_xattrat() and local_set_mapped_file_attrat() to fix (3) and (4) respectively. This partly fixes CVE-2016-9602. Signed-off-by: Greg Kurz <groug@kaod.org> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
parent
d369f20763
commit
38771613ea
@ -978,23 +978,22 @@ static int local_symlink(FsContext *fs_ctx, const char *oldpath,
|
||||
V9fsPath *dir_path, const char *name, FsCred *credp)
|
||||
{
|
||||
int err = -1;
|
||||
int serrno = 0;
|
||||
char *newpath;
|
||||
V9fsString fullname;
|
||||
char *buffer = NULL;
|
||||
int dirfd;
|
||||
|
||||
v9fs_string_init(&fullname);
|
||||
v9fs_string_sprintf(&fullname, "%s/%s", dir_path->data, name);
|
||||
newpath = fullname.data;
|
||||
dirfd = local_opendir_nofollow(fs_ctx, dir_path->data);
|
||||
if (dirfd == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Determine the security model */
|
||||
if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
|
||||
if (fs_ctx->export_flags & V9FS_SM_MAPPED ||
|
||||
fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
|
||||
int fd;
|
||||
ssize_t oldpath_size, write_size;
|
||||
buffer = rpath(fs_ctx, newpath);
|
||||
fd = open(buffer, O_CREAT|O_EXCL|O_RDWR|O_NOFOLLOW, SM_LOCAL_MODE_BITS);
|
||||
|
||||
fd = openat_file(dirfd, name, O_CREAT | O_EXCL | O_RDWR,
|
||||
SM_LOCAL_MODE_BITS);
|
||||
if (fd == -1) {
|
||||
err = fd;
|
||||
goto out;
|
||||
}
|
||||
/* Write the oldpath (target) to the file. */
|
||||
@ -1002,78 +1001,48 @@ static int local_symlink(FsContext *fs_ctx, const char *oldpath,
|
||||
do {
|
||||
write_size = write(fd, (void *)oldpath, oldpath_size);
|
||||
} while (write_size == -1 && errno == EINTR);
|
||||
close_preserve_errno(fd);
|
||||
|
||||
if (write_size != oldpath_size) {
|
||||
serrno = errno;
|
||||
close(fd);
|
||||
err = -1;
|
||||
goto err_end;
|
||||
}
|
||||
close(fd);
|
||||
/* Set cleint credentials in symlink's xattr */
|
||||
credp->fc_mode = credp->fc_mode|S_IFLNK;
|
||||
err = local_set_xattr(buffer, credp);
|
||||
if (err == -1) {
|
||||
serrno = errno;
|
||||
goto err_end;
|
||||
}
|
||||
} else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
|
||||
int fd;
|
||||
ssize_t oldpath_size, write_size;
|
||||
buffer = rpath(fs_ctx, newpath);
|
||||
fd = open(buffer, O_CREAT|O_EXCL|O_RDWR|O_NOFOLLOW, SM_LOCAL_MODE_BITS);
|
||||
if (fd == -1) {
|
||||
err = fd;
|
||||
goto out;
|
||||
}
|
||||
/* Write the oldpath (target) to the file. */
|
||||
oldpath_size = strlen(oldpath);
|
||||
do {
|
||||
write_size = write(fd, (void *)oldpath, oldpath_size);
|
||||
} while (write_size == -1 && errno == EINTR);
|
||||
credp->fc_mode = credp->fc_mode | S_IFLNK;
|
||||
|
||||
if (write_size != oldpath_size) {
|
||||
serrno = errno;
|
||||
close(fd);
|
||||
err = -1;
|
||||
goto err_end;
|
||||
if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
|
||||
err = local_set_xattrat(dirfd, name, credp);
|
||||
} else {
|
||||
err = local_set_mapped_file_attrat(dirfd, name, credp);
|
||||
}
|
||||
close(fd);
|
||||
/* Set cleint credentials in symlink's xattr */
|
||||
credp->fc_mode = credp->fc_mode|S_IFLNK;
|
||||
err = local_set_mapped_file_attr(fs_ctx, newpath, credp);
|
||||
if (err == -1) {
|
||||
serrno = errno;
|
||||
goto err_end;
|
||||
}
|
||||
} else if ((fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) ||
|
||||
(fs_ctx->export_flags & V9FS_SM_NONE)) {
|
||||
buffer = rpath(fs_ctx, newpath);
|
||||
err = symlink(oldpath, buffer);
|
||||
} else if (fs_ctx->export_flags & V9FS_SM_PASSTHROUGH ||
|
||||
fs_ctx->export_flags & V9FS_SM_NONE) {
|
||||
err = symlinkat(oldpath, dirfd, name);
|
||||
if (err) {
|
||||
goto out;
|
||||
}
|
||||
err = lchown(buffer, credp->fc_uid, credp->fc_gid);
|
||||
err = fchownat(dirfd, name, credp->fc_uid, credp->fc_gid,
|
||||
AT_SYMLINK_NOFOLLOW);
|
||||
if (err == -1) {
|
||||
/*
|
||||
* If we fail to change ownership and if we are
|
||||
* using security model none. Ignore the error
|
||||
*/
|
||||
if ((fs_ctx->export_flags & V9FS_SEC_MASK) != V9FS_SM_NONE) {
|
||||
serrno = errno;
|
||||
goto err_end;
|
||||
} else
|
||||
} else {
|
||||
err = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
goto out;
|
||||
|
||||
err_end:
|
||||
remove(buffer);
|
||||
errno = serrno;
|
||||
unlinkat_preserve_errno(dirfd, name, 0);
|
||||
out:
|
||||
g_free(buffer);
|
||||
v9fs_string_free(&fullname);
|
||||
close_preserve_errno(dirfd);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user