| 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;
|
|
|
|
|