Index: src/trusted/service_runtime/sys_filename.c |
diff --git a/src/trusted/service_runtime/sys_filename.c b/src/trusted/service_runtime/sys_filename.c |
index 41edc2385bf1e707f48f3337d16225ffb1a9164f..bde91fc84a5ba12ab47f65136bf2071fe6fb61ab 100644 |
--- a/src/trusted/service_runtime/sys_filename.c |
+++ b/src/trusted/service_runtime/sys_filename.c |
@@ -44,6 +44,215 @@ static uint32_t CopyPathFromUser(struct NaClApp *nap, |
return 0; |
} |
+static int ConstantTimeByteEq(const char *str1, const char *str2, size_t len) { |
+ size_t i = 0; |
+ char v = 0; |
+ |
+ for (i = 0; i < len; i++) { |
+ v |= str1[i] ^ str2[i]; |
+ } |
+ |
+ return (v == 0) ? 1 : 0; |
+} |
+ |
+static uint32_t SanitizeAbsoluteUserPath(char *path, size_t path_cap) { |
+ size_t path_len = 0; |
+ |
+ /* |
+ * If the path is absolute, we just need to sanitize the user path and then |
+ * add the NaClRootDir prefix in that order. |
+ * |
+ * Let's lexically sanitize the given user path. |
+ */ |
+ if (!NaClSanitizePathLexically(path)) { |
+ return -NACL_ABI_EFAULT; |
+ } |
+ |
+ /* |
+ * Make sure we have enough room for the sanitized path and the prefix. |
+ */ |
+ path_len = strlen(path); |
+ if (path_len + NaClRootDirLen + 1 > path_cap) { |
+ return -NACL_ABI_ENAMETOOLONG; |
+ } |
+ |
+ /* |
+ * Shift the user path (and null byte) over. |
+ */ |
+ memmove(path + NaClRootDirLen, path, path_len + 1); |
+ |
+ /* |
+ * Copy the root dir (without null byte) in. |
+ */ |
+ memcpy(path, NaClRootDir, NaClRootDirLen); |
+ |
+ return 0; |
+} |
+ |
+static uint32_t CheckHostPathPrefix(const char *path, size_t path_cap) { |
+ int has_prefix = 0, has_trailing_slash = 0, is_same_length = 0; |
+ /* |
+ * First, let's make sure we can do a constant-time comparison for all |
+ * of the bytes in the expected root dir prefix. We don't want to leak |
+ * any information about the outer filesystem we can avoid leaking, so let's |
+ * just make sure the path buffer has enough space for us to safely check |
+ * the exact same amount of bytes as the RootDir prefix. We'll need to |
+ * include space for the trailing slash. |
+ */ |
+ if (path_cap <= NaClRootDirLen + 1) { |
+ return -NACL_ABI_ENAMETOOLONG; |
+ } |
+ |
+ /* |
+ * Check if the CWD is or is contained by NaClRootDir. |
+ */ |
+ has_prefix = ConstantTimeByteEq(path, NaClRootDir, NaClRootDirLen) ? 1 : 0; |
+ has_trailing_slash = (path[NaClRootDirLen] == '/') ? 1 : 0; |
+ is_same_length = (path[NaClRootDirLen] == '\0') ? 1 : 0; |
+ if ((has_prefix & (has_trailing_slash | is_same_length)) == 0) { |
+ return -NACL_ABI_EFAULT; |
+ } |
+ |
+ return 0; |
+} |
+ |
+static uint32_t SanitizeRelativeUserPath(char *path, size_t path_cap) { |
+ int retval = 0; |
+ size_t path_len = 0, cwd_len = 0; |
+ |
+ /* |
+ * First, we need to make sure the CWD is completely contained by |
+ * NaClRootDir. We have to do this before concatenating the relative path and |
+ * the CWD because otherwise the untrusted application could learn |
+ * information about the outside filesystem. |
+ * |
+ * Let's make some space in our target buffer for the CWD. Move the path to |
+ * the very end of the buffer. |
+ */ |
+ path_len = strlen(path); |
+ memmove(path + path_cap - path_len - 1, path, path_len + 1); |
+ |
+ /* |
+ * Now let's load the CWD into our new buffer space. The space we have is |
+ * path_cap, minus the path itself, minus path's null terminator. |
+ */ |
+ retval = NaClHostDescGetcwd(path, path_cap - path_len - 1); |
+ if (retval != 0) { |
+ return retval; |
+ } |
+ |
+ /* |
+ * Check if the CWD is or is contained by NaClRootDir. |
+ */ |
+ retval = CheckHostPathPrefix(path, path_cap); |
+ if (retval != 0) { |
+ return retval; |
+ } |
+ |
+ /* |
+ * Okay, the CWD is fine. Let's actually concatenate the CWD to the relative |
+ * path. We're safe to just overwrite the null byte with a path separator |
+ * because we don't need the null byte anymore and lexical path sanitization |
+ * will take care of it if for some strange reason the CWD already ends with |
+ * a trailing slash. |
+ */ |
+ cwd_len = strlen(path); |
+ path[cwd_len++] = '/'; |
+ memmove(path + cwd_len, path + path_cap - path_len - 1, path_len + 1); |
+ |
+ /* |
+ * Last, we need to lexically sanitize everything after *NaClRootDir* in the |
+ * path, which doesn't end with a /. It has to be from NaClRootDir on so that |
+ * '..' elements are allowed to go back up to the NaClRootDir but not |
+ * farther. |
+ */ |
+ if (!NaClSanitizePathLexically(path + NaClRootDirLen)) { |
+ return -NACL_ABI_EFAULT; |
+ } |
+ |
+ return 0; |
+} |
+ |
+static uint32_t CopyHostPathFromUser(struct NaClApp *nap, |
+ char *dest, |
+ size_t dest_cap, |
+ uintptr_t src) { |
+ uint32_t retval = -1; |
+ |
+ /* |
+ * The purpose of this function is to not only copy the path bytes from the |
+ * NaClApp, but to make sure the path is properly sanitized and has the |
+ * NaClRootDir prefixed, if provided. |
+ * |
+ * First off, let's get the requested path from the user. |
+ */ |
+ |
+ retval = CopyPathFromUser(nap, dest, dest_cap, src); |
+ |
+ /* |
+ * Do we even have anything to do? If there's no NaClRootDir or getting the |
+ * path failed, then passing through is the expected behavior. |
+ */ |
+ if (NaClRootDir == NULL || retval != 0) { |
+ return retval; |
+ } |
+ |
+ /* |
+ * We should have gotten back something. |
+ */ |
+ if (dest_cap <= 0 || dest[0] == '\0') { |
+ return -NACL_ABI_ENOENT; |
+ } |
+ |
+ /* |
+ * Check if we have a relative or absolute path. |
+ */ |
+ if (dest[0] == '/') { |
+ retval = SanitizeAbsoluteUserPath(dest, dest_cap); |
+ } else { |
+ retval = SanitizeRelativeUserPath(dest, dest_cap); |
+ } |
+ return retval; |
+} |
+ |
+static int NaClCopyHostPathOutToUser(struct NaClApp *nap, |
+ uintptr_t dst_usr_addr, |
+ char *path, |
+ size_t path_cap) { |
+ uint32_t retval = 0; |
+ |
+ /* |
+ * Check if we need to perform any path sanitization |
+ */ |
+ if (NaClRootDir == NULL) { |
+ return NaClCopyOutToUser(nap, dst_usr_addr, path, strlen(path) + 1); |
+ } |
+ |
+ /* |
+ * Okay, let's make sure the path we just got starts with the root dir. |
+ */ |
+ retval = CheckHostPathPrefix(path, path_cap); |
+ if (retval != 0) { |
+ return 0; |
+ } |
+ |
+ /* |
+ * In this case, the path we're copying out *is* NaClRootDir, which means |
+ * it doesn't end with a trailing slash, and all we want to do is return |
+ * a slash. Special case. |
+ */ |
+ if (path[NaClRootDirLen] == '\0') { |
+ return NaClCopyOutToUser(nap, dst_usr_addr, "/", 2); |
+ } |
+ |
+ /* |
+ * Okay we're set. Copy out everything after the root dir (including the |
+ * slash). |
+ */ |
+ return NaClCopyOutToUser(nap, dst_usr_addr, path + NaClRootDirLen, |
+ strlen(path + NaClRootDirLen) + 1); |
+} |
+ |
int32_t NaClSysOpen(struct NaClAppThread *natp, |
uint32_t pathname, |
int flags, |
@@ -58,11 +267,11 @@ int32_t NaClSysOpen(struct NaClAppThread *natp, |
"0x%08"NACL_PRIx32", 0x%x, 0x%x)\n", |
(uintptr_t) natp, pathname, flags, mode); |
- if (!NaClAclBypassChecks) { |
+ if (!NaClAclBypassChecks && NaClRootDir == NULL) { |
return -NACL_ABI_EACCES; |
} |
- retval = CopyPathFromUser(nap, path, sizeof path, (uintptr_t) pathname); |
+ retval = CopyHostPathFromUser(nap, path, sizeof path, (uintptr_t) pathname); |
if (0 != retval) |
goto cleanup; |
@@ -166,11 +375,11 @@ int32_t NaClSysStat(struct NaClAppThread *natp, |
("Entered NaClSysStat(0x%08"NACL_PRIxPTR", 0x%08"NACL_PRIx32"," |
" 0x%08"NACL_PRIx32")\n"), (uintptr_t) natp, pathname, nasp); |
- if (!NaClAclBypassChecks) { |
+ if (!NaClAclBypassChecks && NaClRootDir == NULL) { |
return -NACL_ABI_EACCES; |
} |
- retval = CopyPathFromUser(nap, path, sizeof path, pathname); |
+ retval = CopyHostPathFromUser(nap, path, sizeof path, pathname); |
if (0 != retval) |
goto cleanup; |
@@ -197,12 +406,12 @@ int32_t NaClSysMkdir(struct NaClAppThread *natp, |
char path[NACL_CONFIG_PATH_MAX]; |
int32_t retval = -NACL_ABI_EINVAL; |
- if (!NaClAclBypassChecks) { |
+ if (!NaClAclBypassChecks && NaClRootDir == NULL) { |
retval = -NACL_ABI_EACCES; |
goto cleanup; |
} |
- retval = CopyPathFromUser(nap, path, sizeof path, pathname); |
+ retval = CopyHostPathFromUser(nap, path, sizeof path, pathname); |
if (0 != retval) |
goto cleanup; |
@@ -217,12 +426,12 @@ int32_t NaClSysRmdir(struct NaClAppThread *natp, |
char path[NACL_CONFIG_PATH_MAX]; |
int32_t retval = -NACL_ABI_EINVAL; |
- if (!NaClAclBypassChecks) { |
+ if (!NaClAclBypassChecks && NaClRootDir == NULL) { |
retval = -NACL_ABI_EACCES; |
goto cleanup; |
} |
- retval = CopyPathFromUser(nap, path, sizeof path, pathname); |
+ retval = CopyHostPathFromUser(nap, path, sizeof path, pathname); |
if (0 != retval) |
goto cleanup; |
@@ -237,12 +446,12 @@ int32_t NaClSysChdir(struct NaClAppThread *natp, |
char path[NACL_CONFIG_PATH_MAX]; |
int32_t retval = -NACL_ABI_EINVAL; |
- if (!NaClAclBypassChecks) { |
+ if (!NaClAclBypassChecks && NaClRootDir == NULL) { |
retval = -NACL_ABI_EACCES; |
goto cleanup; |
} |
- retval = CopyPathFromUser(nap, path, sizeof path, pathname); |
+ retval = CopyHostPathFromUser(nap, path, sizeof path, pathname); |
if (0 != retval) |
goto cleanup; |
@@ -258,7 +467,7 @@ int32_t NaClSysGetcwd(struct NaClAppThread *natp, |
int32_t retval = -NACL_ABI_EINVAL; |
char path[NACL_CONFIG_PATH_MAX]; |
- if (!NaClAclBypassChecks) { |
+ if (!NaClAclBypassChecks && NaClRootDir == NULL) { |
retval = -NACL_ABI_EACCES; |
goto cleanup; |
} |
@@ -270,7 +479,7 @@ int32_t NaClSysGetcwd(struct NaClAppThread *natp, |
if (retval != 0) |
goto cleanup; |
- if (!NaClCopyOutToUser(nap, buffer, &path, strlen(path) + 1)) |
+ if (!NaClCopyHostPathOutToUser(nap, buffer, path, sizeof path)) |
retval = -NACL_ABI_EFAULT; |
cleanup: |
@@ -283,12 +492,12 @@ int32_t NaClSysUnlink(struct NaClAppThread *natp, |
char path[NACL_CONFIG_PATH_MAX]; |
int32_t retval = -NACL_ABI_EINVAL; |
- if (!NaClAclBypassChecks) { |
+ if (!NaClAclBypassChecks && NaClRootDir == NULL) { |
retval = -NACL_ABI_EACCES; |
goto cleanup; |
} |
- retval = CopyPathFromUser(nap, path, sizeof path, pathname); |
+ retval = CopyHostPathFromUser(nap, path, sizeof path, pathname); |
if (0 != retval) |
goto cleanup; |
@@ -306,10 +515,10 @@ int32_t NaClSysTruncate(struct NaClAppThread *natp, |
int32_t retval = -NACL_ABI_EINVAL; |
nacl_abi_off_t length; |
- if (!NaClAclBypassChecks) |
+ if (!NaClAclBypassChecks && NaClRootDir == NULL) |
return -NACL_ABI_EACCES; |
- retval = CopyPathFromUser(nap, path, sizeof path, pathname); |
+ retval = CopyHostPathFromUser(nap, path, sizeof path, pathname); |
if (0 != retval) |
return retval; |
@@ -334,16 +543,16 @@ int32_t NaClSysLstat(struct NaClAppThread *natp, |
("Entered NaClSysLstat(0x%08"NACL_PRIxPTR", 0x%08"NACL_PRIx32"," |
" 0x%08"NACL_PRIx32")\n"), (uintptr_t) natp, pathname, nasp); |
- if (!NaClAclBypassChecks) { |
+ if (!NaClAclBypassChecks && NaClRootDir == NULL) { |
return -NACL_ABI_EACCES; |
} |
- retval = CopyPathFromUser(nap, path, sizeof path, pathname); |
+ retval = CopyHostPathFromUser(nap, path, sizeof path, pathname); |
if (0 != retval) |
return retval; |
/* |
- * Perform a host stat. |
+ * Perform a host lstat directly |
*/ |
retval = NaClHostDescLstat(path, &stbuf); |
if (0 == retval) { |
@@ -365,14 +574,14 @@ int32_t NaClSysLink(struct NaClAppThread *natp, |
char newpath[NACL_CONFIG_PATH_MAX]; |
int32_t retval = -NACL_ABI_EINVAL; |
- if (!NaClAclBypassChecks) |
+ if (!NaClAclBypassChecks && NaClRootDir == NULL) |
return -NACL_ABI_EACCES; |
- retval = CopyPathFromUser(nap, oldpath, sizeof oldpath, oldname); |
+ retval = CopyHostPathFromUser(nap, oldpath, sizeof oldpath, oldname); |
if (0 != retval) |
return retval; |
- retval = CopyPathFromUser(nap, newpath, sizeof newpath, newname); |
+ retval = CopyHostPathFromUser(nap, newpath, sizeof newpath, newname); |
if (0 != retval) |
return retval; |
@@ -387,14 +596,14 @@ int32_t NaClSysRename(struct NaClAppThread *natp, |
char newpath[NACL_CONFIG_PATH_MAX]; |
int32_t retval = -NACL_ABI_EINVAL; |
- if (!NaClAclBypassChecks) |
+ if (!NaClAclBypassChecks && NaClRootDir == NULL) |
return -NACL_ABI_EACCES; |
- retval = CopyPathFromUser(nap, oldpath, sizeof oldpath, oldname); |
+ retval = CopyHostPathFromUser(nap, oldpath, sizeof oldpath, oldname); |
if (0 != retval) |
return retval; |
- retval = CopyPathFromUser(nap, newpath, sizeof newpath, newname); |
+ retval = CopyHostPathFromUser(nap, newpath, sizeof newpath, newname); |
if (0 != retval) |
return retval; |
@@ -409,6 +618,15 @@ int32_t NaClSysSymlink(struct NaClAppThread *natp, |
char newpath[NACL_CONFIG_PATH_MAX]; |
int32_t retval = -NACL_ABI_EINVAL; |
+ if (NaClRootDir != NULL) { |
+ /* |
+ * Mounted root folders don't support symlinks yet, so pretend they don't |
+ * exist and can't be created. See also lstat and readlink. |
+ * Could be convinced to do ENOSYS instead of EPERM. |
+ */ |
+ return -NACL_ABI_EPERM; |
+ } |
+ |
if (!NaClAclBypassChecks) |
return -NACL_ABI_EACCES; |
@@ -430,10 +648,10 @@ int32_t NaClSysChmod(struct NaClAppThread *natp, |
char pathname[NACL_CONFIG_PATH_MAX]; |
int32_t retval = -NACL_ABI_EINVAL; |
- if (!NaClAclBypassChecks) |
+ if (!NaClAclBypassChecks && NaClRootDir == NULL) |
return -NACL_ABI_EACCES; |
- retval = CopyPathFromUser(nap, pathname, sizeof pathname, path); |
+ retval = CopyHostPathFromUser(nap, pathname, sizeof pathname, path); |
if (0 != retval) |
return retval; |
@@ -447,7 +665,7 @@ int32_t NaClSysAccess(struct NaClAppThread *natp, |
char pathname[NACL_CONFIG_PATH_MAX]; |
int32_t retval = -NACL_ABI_EINVAL; |
- if (!NaClAclBypassChecks) |
+ if (!NaClAclBypassChecks && NaClRootDir == NULL) |
return -NACL_ABI_EACCES; |
/* |
@@ -457,7 +675,7 @@ int32_t NaClSysAccess(struct NaClAppThread *natp, |
&& (amode & ~(NACL_ABI_R_OK | NACL_ABI_W_OK | NACL_ABI_X_OK)) != 0) |
return -NACL_ABI_EINVAL; |
- retval = CopyPathFromUser(nap, pathname, sizeof pathname, path); |
+ retval = CopyHostPathFromUser(nap, pathname, sizeof pathname, path); |
if (0 != retval) |
return retval; |
@@ -476,10 +694,10 @@ int32_t NaClSysReadlink(struct NaClAppThread *natp, |
int32_t retval = -NACL_ABI_EINVAL; |
uint32_t result_size; |
- if (!NaClAclBypassChecks) |
+ if (!NaClAclBypassChecks && NaClRootDir == NULL) |
return -NACL_ABI_EACCES; |
- retval = CopyPathFromUser(nap, pathname, sizeof pathname, path); |
+ retval = CopyHostPathFromUser(nap, pathname, sizeof pathname, path); |
if (0 != retval) |
return retval; |
@@ -518,10 +736,10 @@ int32_t NaClSysUtimes(struct NaClAppThread *natp, |
char pathname[NACL_CONFIG_PATH_MAX]; |
int32_t retval = -NACL_ABI_EINVAL; |
- if (!NaClAclBypassChecks) |
+ if (!NaClAclBypassChecks && NaClRootDir == NULL) |
return -NACL_ABI_EACCES; |
- retval = CopyPathFromUser(nap, pathname, sizeof pathname, path); |
+ retval = CopyHostPathFromUser(nap, pathname, sizeof pathname, path); |
if (0 != retval) |
return retval; |