aboutsummaryrefslogtreecommitdiff
path: root/src/common
diff options
context:
space:
mode:
authorNick Mathewson <nickm@torproject.org>2016-02-16 11:34:06 -0500
committerNick Mathewson <nickm@torproject.org>2016-02-16 11:34:06 -0500
commit5cd6c577df3b3f59a8380b03e45d437aa593d326 (patch)
tree1940f5c05a6abec7f024c15c6f7ceb51e9c85986 /src/common
parent1f679d4ae11cd976f5539bc4ddf36873132aeb00 (diff)
parent75daeb5c6d9dd549b92f20edbd795c85e451cbcd (diff)
downloadtor-5cd6c577df3b3f59a8380b03e45d437aa593d326.tar.gz
tor-5cd6c577df3b3f59a8380b03e45d437aa593d326.zip
Merge branch 'bug17852_revised'
Diffstat (limited to 'src/common')
-rw-r--r--src/common/compat.c7
-rw-r--r--src/common/util.c73
2 files changed, 68 insertions, 12 deletions
diff --git a/src/common/compat.c b/src/common/compat.c
index fb22e922ba..87f56644cb 100644
--- a/src/common/compat.c
+++ b/src/common/compat.c
@@ -576,14 +576,17 @@ tor_vasprintf(char **strp, const char *fmt, va_list args)
int len, r;
va_list tmp_args;
va_copy(tmp_args, args);
- len = vsnprintf(buf, sizeof(buf), fmt, tmp_args);
+ /* vsnprintf() was properly checked but tor_vsnprintf() available so
+ * why not use it? */
+ len = tor_vsnprintf(buf, sizeof(buf), fmt, tmp_args);
va_end(tmp_args);
if (len < (int)sizeof(buf)) {
*strp = tor_strdup(buf);
return len;
}
strp_tmp = tor_malloc(len+1);
- r = vsnprintf(strp_tmp, len+1, fmt, args);
+ /* use of tor_vsnprintf() will ensure string is null terminated */
+ r = tor_vsnprintf(strp_tmp, len+1, fmt, args);
if (r != len) {
tor_free(strp_tmp);
*strp = NULL;
diff --git a/src/common/util.c b/src/common/util.c
index 04f48a4eee..fda5993edf 100644
--- a/src/common/util.c
+++ b/src/common/util.c
@@ -2056,9 +2056,10 @@ int
check_private_dir(const char *dirname, cpd_check_t check,
const char *effective_user)
{
+ int fd;
int r;
struct stat st;
- char *f;
+ //char *f;
#ifndef _WIN32
unsigned unwanted_bits = 0;
const struct passwd *pw = NULL;
@@ -2068,18 +2069,34 @@ check_private_dir(const char *dirname, cpd_check_t check,
(void)effective_user;
#endif
+ /*
+ * Goal is to harden the implementation by removing any
+ * potential for race between stat() and chmod().
+ * chmod() accepts filename as argument. If an attacker can move
+ * the file between stat() and chmod(), a potential race exists.
+ *
+ * Several suggestions taken from:
+ * https://developer.apple.com/library/mac/documentation/Security/Conceptual/SecureCodingGuide/Articles/RaceConditions.html
+ */
tor_assert(dirname);
- f = tor_strdup(dirname);
- clean_name_for_stat(f);
- log_debug(LD_FS, "stat()ing %s", f);
- r = stat(sandbox_intern_string(f), &st);
- tor_free(f);
- if (r) {
+
+ /* Open directory.
+ * O_NOFOLLOW to ensure that it does not follow symbolic links */
+ fd = open(sandbox_intern_string(dirname), O_NOFOLLOW);
+
+ /* Was there an error? Maybe the directory does not exist? */
+ if (fd == -1) {
+
if (errno != ENOENT) {
+ /* Other directory error */
log_warn(LD_FS, "Directory %s cannot be read: %s", dirname,
strerror(errno));
return -1;
}
+
+ /* Received ENOENT: Directory does not exist */
+
+ /* Should we create the directory? */
if (check & CPD_CREATE) {
log_info(LD_GENERAL, "Creating directory %s", dirname);
#if defined (_WIN32)
@@ -2091,23 +2108,51 @@ check_private_dir(const char *dirname, cpd_check_t check,
r = mkdir(dirname, 0700);
}
#endif
+
+ /* check for mkdir() error */
if (r) {
log_warn(LD_FS, "Error creating directory %s: %s", dirname,
strerror(errno));
return -1;
}
- } else if (!(check & CPD_CHECK)) {
+
+ /* we just created the directory. try to open it again.
+ * permissions on the directory will be checked again below.*/
+ fd = open(sandbox_intern_string(dirname), O_NOFOLLOW);
+
+ if ( fd == -1 ) return -1;
+
+ } else if (!(check & CPD_CHECK)) {
log_warn(LD_FS, "Directory %s does not exist.", dirname);
return -1;
}
+
/* XXXX In the case where check==CPD_CHECK, we should look at the
* parent directory a little harder. */
return 0;
}
+
+ tor_assert(fd);
+
+ //f = tor_strdup(dirname);
+ //clean_name_for_stat(f);
+ log_debug(LD_FS, "stat()ing %s", dirname);
+ //r = stat(sandbox_intern_string(f), &st);
+ r = fstat(fd, &st);
+ if (r == -1) {
+ log_warn(LD_FS, "fstat() on directory %s failed.", dirname);
+ close(fd);
+ return -1;
+ }
+ //tor_free(f);
+
+ /* check that dirname is a directory */
if (!(st.st_mode & S_IFDIR)) {
log_warn(LD_FS, "%s is not a directory", dirname);
+ close(fd);
return -1;
}
+
#ifndef _WIN32
if (effective_user) {
/* Look up the user and group information.
@@ -2124,7 +2169,6 @@ check_private_dir(const char *dirname, cpd_check_t check,
running_uid = getuid();
running_gid = getgid();
}
-
if (st.st_uid != running_uid) {
const struct passwd *pw = NULL;
char *process_ownername = NULL;
@@ -2140,6 +2184,7 @@ check_private_dir(const char *dirname, cpd_check_t check,
pw ? pw->pw_name : "<unknown>", (int)st.st_uid);
tor_free(process_ownername);
+ close(fd);
return -1;
}
if ( (check & (CPD_GROUP_OK|CPD_GROUP_READ))
@@ -2156,6 +2201,7 @@ check_private_dir(const char *dirname, cpd_check_t check,
gr ? gr->gr_name : "<unknown>", (int)st.st_gid);
tor_free(process_groupname);
+ close(fd);
return -1;
}
if (check & (CPD_GROUP_OK|CPD_GROUP_READ)) {
@@ -2168,6 +2214,7 @@ check_private_dir(const char *dirname, cpd_check_t check,
if (check & CPD_CHECK_MODE_ONLY) {
log_warn(LD_FS, "Permissions on directory %s are too permissive.",
dirname);
+ close(fd);
return -1;
}
log_warn(LD_FS, "Fixing permissions on directory %s", dirname);
@@ -2177,15 +2224,18 @@ check_private_dir(const char *dirname, cpd_check_t check,
new_mode |= 0050; /* Group should have rx */
}
new_mode &= ~unwanted_bits; /* Clear the bits that we didn't want set...*/
- if (chmod(dirname, new_mode)) {
+ if (fchmod(fd, new_mode)) {
log_warn(LD_FS, "Could not chmod directory %s: %s", dirname,
strerror(errno));
+ close(fd);
return -1;
} else {
+ close(fd);
return 0;
}
}
#endif
+ close(fd);
return 0;
}
@@ -2900,6 +2950,9 @@ expand_filename(const char *filename)
{
tor_assert(filename);
#ifdef _WIN32
+ /* Might consider using GetFullPathName() as described here:
+ * http://etutorials.org/Programming/secure+programming/Chapter+3.+Input+Validation/3.7+Validating+Filenames+and+Paths/
+ */
return tor_strdup(filename);
#else
if (*filename == '~') {