Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Address CVE-2013-4235 #545

Merged
merged 7 commits into from
Aug 17, 2022
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 49 additions & 81 deletions libmisc/chowndir.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,45 +17,28 @@
#include "defines.h"
#include <fcntl.h>
#include <stdio.h>
/*
* chown_tree - change ownership of files in a directory tree
*
* chown_dir() walks a directory tree and changes the ownership
* of all files owned by the provided user ID.
*
* Only files owned (resp. group-owned) by old_uid (resp. by old_gid)
* will have their ownership (resp. group-ownership) modified, unless
* old_uid (resp. old_gid) is set to -1.
*
* new_uid and new_gid can be set to -1 to indicate that no owner or
* group-owner shall be changed.
*/
int chown_tree (const char *root,
#include <unistd.h>

static int chown_tree_at (int at_fd,
const char *path,
uid_t old_uid,
uid_t new_uid,
gid_t old_gid,
gid_t new_gid)
{
char *new_name;
size_t new_name_len;
int rc = 0;
struct dirent *ent;
struct stat sb;
DIR *dir;
const struct dirent *ent;
struct stat dir_sb;
int dir_fd, rc = 0;

new_name = malloc (1024);
if (NULL == new_name) {
dir_fd = openat (at_fd, path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
if (dir_fd < 0) {
return -1;
}
new_name_len = 1024;

/*
* Make certain the directory exists. This routine is called
* directly by the invoker, or recursively.
*/

if (access (root, F_OK) != 0) {
free (new_name);
dir = fdopendir (dir_fd);
if (!dir) {
(void) close (dir_fd);
return -1;
}

Expand All @@ -65,68 +48,34 @@ int chown_tree (const char *root,
* recursively. If not, it is checked to see if an ownership
* shall be changed.
*/

dir = opendir (root);
if (NULL == dir) {
free (new_name);
return -1;
}

while ((ent = readdir (dir))) {
size_t ent_name_len;
uid_t tmpuid = (uid_t) -1;
gid_t tmpgid = (gid_t) -1;
struct stat ent_sb;

/*
* Skip the "." and ".." entries
*/

if ( (strcmp (ent->d_name, ".") == 0)
|| (strcmp (ent->d_name, "..") == 0)) {
continue;
}

/*
* Make the filename for both the source and the
* destination files.
*/

ent_name_len = strlen (root) + strlen (ent->d_name) + 2;
if (ent_name_len > new_name_len) {
/*@only@*/char *tmp = realloc (new_name, ent_name_len);
if (NULL == tmp) {
rc = -1;
break;
}
new_name = tmp;
new_name_len = ent_name_len;
}

(void) snprintf (new_name, new_name_len, "%s/%s", root, ent->d_name);

/* Don't follow symbolic links! */
if (LSTAT (new_name, &sb) == -1) {
continue;
rc = fstatat (dirfd(dir), ent->d_name, &ent_sb, AT_SYMLINK_NOFOLLOW);
hallyn marked this conversation as resolved.
Show resolved Hide resolved
if (rc < 0) {
break;
hallyn marked this conversation as resolved.
Show resolved Hide resolved
}

if (S_ISDIR (sb.st_mode) && !S_ISLNK (sb.st_mode)) {

if (S_ISDIR (ent_sb.st_mode)) {
/*
* Do the entire subdirectory.
*/

rc = chown_tree (new_name, old_uid, new_uid,
old_gid, new_gid);
rc = chown_tree_at (dirfd(dir), ent->d_name, old_uid, new_uid, old_gid, new_gid);
hallyn marked this conversation as resolved.
Show resolved Hide resolved
if (0 != rc) {
break;
}
}
#ifndef HAVE_LCHOWN
/* don't use chown (follows symbolic links!) */
if (S_ISLNK (sb.st_mode)) {
continue;
}
#endif

/*
* By default, the IDs are not changed (-1).
*
Expand All @@ -136,43 +85,62 @@ int chown_tree (const char *root,
* If the file is not group-owned by the group, the
* group-owner is not changed.
*/
if (((uid_t) -1 == old_uid) || (sb.st_uid == old_uid)) {
if (((uid_t) -1 == old_uid) || (ent_sb.st_uid == old_uid)) {
tmpuid = new_uid;
}
if (((gid_t) -1 == old_gid) || (sb.st_gid == old_gid)) {
if (((gid_t) -1 == old_gid) || (ent_sb.st_gid == old_gid)) {
tmpgid = new_gid;
}
if (((uid_t) -1 != tmpuid) || ((gid_t) -1 != tmpgid)) {
rc = LCHOWN (new_name, tmpuid, tmpgid);
rc = fchownat (dirfd(dir), ent->d_name, tmpuid, tmpgid, AT_SYMLINK_NOFOLLOW);
if (0 != rc) {
break;
}
}
}

free (new_name);
(void) closedir (dir);

/*
* Now do the root of the tree
*/

if ((0 == rc) && (stat (root, &sb) == 0)) {
if ((0 == rc) && (fstat (dirfd(dir), &dir_sb) == 0)) {
uid_t tmpuid = (uid_t) -1;
gid_t tmpgid = (gid_t) -1;
if (((uid_t) -1 == old_uid) || (sb.st_uid == old_uid)) {
if (((uid_t) -1 == old_uid) || (dir_sb.st_uid == old_uid)) {
tmpuid = new_uid;
}
if (((gid_t) -1 == old_gid) || (sb.st_gid == old_gid)) {
if (((gid_t) -1 == old_gid) || (dir_sb.st_gid == old_gid)) {
tmpgid = new_gid;
}
if (((uid_t) -1 != tmpuid) || ((gid_t) -1 != tmpgid)) {
rc = LCHOWN (root, tmpuid, tmpgid);
rc = fchown (dirfd(dir), tmpuid, tmpgid);
}
} else {
rc = -1;
}

(void) closedir (dir);

return rc;
}

/*
* chown_tree - change ownership of files in a directory tree
*
* chown_dir() walks a directory tree and changes the ownership
* of all files owned by the provided user ID.
*
* Only files owned (resp. group-owned) by old_uid (resp. by old_gid)
* will have their ownership (resp. group-ownership) modified, unless
* old_uid (resp. old_gid) is set to -1.
*
* new_uid and new_gid can be set to -1 to indicate that no owner or
* group-owner shall be changed.
*/
int chown_tree (const char *root,
uid_t old_uid,
uid_t new_uid,
gid_t old_gid,
gid_t new_gid)
{
return chown_tree_at (AT_FDCWD, root, old_uid, new_uid, old_gid, new_gid);
}