| /* |
| * Copyright 2016 The Native Client Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| /* |
| * NaCl tests for limited file access |
| */ |
| |
| #include <dirent.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <limits.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| |
| #include "native_client/src/include/nacl_assert.h" |
| #include "native_client/src/trusted/service_runtime/nacl_config.h" |
| |
| namespace { |
| |
| /* Global pathnames (randomly generated by SCons) */ |
| char g_temp_dir_name[PATH_MAX]; |
| char g_temp_dir_path[PATH_MAX]; |
| char g_temp_file_name[PATH_MAX]; |
| char g_temp_file_path[PATH_MAX]; |
| char g_temp_symlink_name[PATH_MAX]; |
| char g_temp_symlink_path[PATH_MAX]; |
| char g_temp_sub_dir_name[PATH_MAX]; |
| char g_temp_sub_dir_path[PATH_MAX]; |
| char g_temp_sub_file_name[PATH_MAX]; |
| char g_temp_sub_file_path[PATH_MAX]; |
| char g_temp_inaccessible_dir_name[PATH_MAX]; |
| char g_temp_inaccessible_file_name[PATH_MAX]; |
| |
| /* |
| * function passed(testname, msg) |
| * print success message |
| */ |
| void passed(const char *testname, const char *msg) { |
| printf("TEST PASSED: %s: %s\n", testname, msg); |
| } |
| |
| /* |
| * Helper function which tests out basic file access. |
| * Opens a file (possibly new), |
| * Writes a test string to the file, |
| * Seeks to the beginning of the file, |
| * Reads, verifying the original write persisted, and |
| * Closes the file. |
| * |
| * @param[in] filename Name of the file which is opened. |
| * @param[in] new_file Boolean indicating if the |filename| to be opened is new. |
| */ |
| void do_test_write_read_file(const char *filename, bool new_file) { |
| printf("File: %s\n", filename); |
| char buf[5]; |
| char test_string[6] = "abcde"; |
| int test_string_len = 5; |
| int new_file_flags = new_file ? (O_CREAT | O_TRUNC) : 0; |
| int fd = open(filename, O_RDWR | new_file_flags, S_IRUSR | S_IWUSR); |
| ASSERT_MSG(fd >= 0, "open() failed\n"); |
| |
| ASSERT_EQ(test_string_len, write(fd, test_string, test_string_len)); |
| ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)); |
| ASSERT_EQ(test_string_len, read(fd, buf, test_string_len)); |
| ASSERT_EQ(0, memcmp(buf, test_string, test_string_len)); |
| ASSERT_EQ(0, close(fd)); |
| } |
| |
| void test_directory_walk() { |
| // Attempt to walk down valid directory structure (and back again). |
| char dirname[PATH_MAX]; |
| ASSERT_NE_MSG(getcwd(dirname, PATH_MAX), NULL, "getcwd() failed"); |
| ASSERT_EQ(strcmp(dirname, "/"), 0); |
| |
| ASSERT_EQ_MSG(chdir("."), 0, "chdir() failed"); |
| ASSERT_EQ_MSG(chdir("/"), 0, "chdir() failed"); |
| |
| DIR *d = opendir(dirname); |
| ASSERT_NE_MSG(d, NULL, "opendir() failed"); |
| int count = 0; |
| struct dirent *ent; |
| |
| /* |
| * We expect to see: |
| * temp_file |
| * temp_symlink |
| * sub_temp_dir |
| * .. |
| * . |
| */ |
| |
| bool temp_file_seen = false; |
| bool temp_symlink_seen = false; |
| bool sub_temp_dir_seen = false; |
| bool parent_directory_seen = false; |
| bool current_directory_seen = false; |
| while (1) { |
| ent = readdir(d); |
| if (!ent) |
| break; |
| count++; |
| if (!strncmp(ent->d_name, g_temp_file_name, strlen(g_temp_file_name))) { |
| temp_file_seen = true; |
| } else if (!strncmp(ent->d_name, g_temp_symlink_name, |
| strlen(g_temp_symlink_name))) { |
| temp_symlink_seen = true; |
| } else if (!strncmp(ent->d_name, g_temp_sub_dir_name, |
| strlen(g_temp_sub_dir_name))) { |
| sub_temp_dir_seen = true; |
| } else if (!strncmp(ent->d_name, "..", 2)) { |
| parent_directory_seen = true; |
| } else if (!strncmp(ent->d_name, ".", 1)) { |
| current_directory_seen = true; |
| } |
| } |
| ASSERT_EQ_MSG(closedir(d), 0, "closedir() failed"); |
| |
| ASSERT(temp_file_seen); |
| ASSERT(temp_symlink_seen); |
| ASSERT(sub_temp_dir_seen); |
| ASSERT(parent_directory_seen); |
| ASSERT(current_directory_seen); |
| ASSERT_EQ(count, 5); |
| |
| // Chdir with relative path name |
| ASSERT_EQ_MSG(chdir(g_temp_sub_dir_name), 0, "chdir() failed"); |
| ASSERT_NE_MSG(getcwd(dirname, PATH_MAX), NULL, "getcwd() failed"); |
| ASSERT_EQ(strcmp(dirname, g_temp_sub_dir_path), 0); |
| |
| // Chdir with absolute path name |
| ASSERT_EQ_MSG(chdir(g_temp_sub_dir_path), 0, "chdir() failed"); |
| ASSERT_NE_MSG(getcwd(dirname, PATH_MAX), NULL, "getcwd() failed"); |
| ASSERT_EQ(strcmp(dirname, g_temp_sub_dir_path), 0); |
| |
| d = opendir(dirname); |
| count = 0; |
| |
| /* |
| * We expect to see: |
| * temp_sub_file |
| * .. |
| * . |
| */ |
| |
| bool sub_temp_file_seen = false; |
| parent_directory_seen = false; |
| current_directory_seen = false; |
| while (1) { |
| ent = readdir(d); |
| if (!ent) |
| break; |
| count++; |
| if (!strncmp(ent->d_name, g_temp_sub_file_name, |
| strlen(g_temp_sub_file_name))) { |
| sub_temp_file_seen = true; |
| } else if (!strncmp(ent->d_name, "..", 2)) { |
| parent_directory_seen = true; |
| } else if (!strncmp(ent->d_name, ".", 1)) { |
| current_directory_seen = true; |
| } |
| } |
| ASSERT_EQ_MSG(closedir(d), 0, "closedir() failed"); |
| |
| ASSERT(sub_temp_file_seen); |
| ASSERT(parent_directory_seen); |
| ASSERT(current_directory_seen); |
| ASSERT_EQ(count, 3); |
| |
| ASSERT_EQ_MSG(chdir("/"), 0, "chdir() failed"); |
| ASSERT_NE_MSG(getcwd(dirname, PATH_MAX), NULL, "getcwd() failed"); |
| ASSERT_EQ(strcmp(dirname, "/"), 0); |
| |
| passed("test_directory_walk", "all"); |
| } |
| |
| void test_new_directory_access() { |
| // Create a new directory, removes that directory. |
| mode_t mode = S_IRUSR | S_IWUSR | S_IXUSR; |
| ASSERT_EQ(mkdir("/test_dir", mode), 0); |
| ASSERT_EQ(rmdir("/test_dir"), 0); |
| |
| ASSERT_EQ(mkdir("/test_dir/", mode), 0); |
| ASSERT_EQ(rmdir("/test_dir/"), 0); |
| |
| // Test that relative paths can also be used. |
| ASSERT_EQ(mkdir("test_dir", mode), 0); |
| ASSERT_EQ(rmdir("test_dir"), 0); |
| |
| // Test that directory contents cannot be accessed by relative path if the cwd |
| // is no longer valid. |
| ASSERT_EQ(mkdir("sub_dir", mode), 0); |
| ASSERT_EQ(chdir("sub_dir"), 0); |
| ASSERT_EQ(rmdir("../sub_dir"), 0); |
| ASSERT_EQ(open("xxx", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR), -1); |
| ASSERT_EQ(errno, ENOENT); |
| ASSERT_EQ(chdir("/"), 0); |
| |
| char file_name[PATH_MAX]; |
| snprintf(file_name, PATH_MAX, "%s/test_dir", g_temp_sub_dir_path); |
| ASSERT_EQ(mkdir(file_name, mode), 0); |
| ASSERT_EQ(rmdir(file_name), 0); |
| |
| ASSERT_NE(mkdir("/this_dir_does_not_exist/sub_dir", mode), 0); |
| passed("test_new_directory_access", "all"); |
| } |
| |
| void test_link_access() { |
| // Tests that we can create hard links within mounted directory. |
| |
| // Tests we can make hard link to a temporary file in the root directory. |
| ASSERT_EQ(link(g_temp_file_path, "/temp_file_hard_link"), 0); |
| ASSERT_EQ(unlink("/temp_file_hard_link"), 0); |
| |
| char link_name[PATH_MAX]; |
| // Tests we can make hard link to a temporary file in a subdirectory. |
| snprintf(link_name, PATH_MAX, "%s/temp_file_hard_link", g_temp_sub_dir_path); |
| ASSERT_EQ(link(g_temp_file_path, link_name), 0); |
| ASSERT_EQ(unlink(link_name), 0); |
| passed("test_link_access", "all"); |
| } |
| |
| void test_symlink_access() { |
| // Tests that symlink and readlink access are disabled. |
| char symlink_name[] = "temp_file_symlink"; |
| char link_dest[PATH_MAX]; |
| |
| // Symlink is disabled |
| ASSERT_EQ(symlink(g_temp_file_path, symlink_name), -1); |
| ASSERT_EQ(errno, EACCES); |
| // Readlink is disabled |
| ASSERT_EQ(readlink(g_temp_symlink_path, link_dest, sizeof link_dest), -1); |
| ASSERT_EQ(errno, EACCES); |
| // Cannot open symlinks (even if they already exist) |
| ASSERT_EQ(open(g_temp_symlink_path, O_RDONLY), -1); |
| ASSERT_EQ(errno, EACCES); |
| |
| passed("test_symlink_access", "all"); |
| } |
| |
| void test_rename_access() { |
| // Demonstrates that files can be renamed within the mounted directory. |
| char new_file_location[PATH_MAX]; |
| snprintf(new_file_location, PATH_MAX, "/%s/temp_file_new", |
| g_temp_sub_dir_name); |
| ASSERT_EQ(rename(g_temp_file_path, new_file_location), 0); |
| ASSERT_EQ(rename(new_file_location, g_temp_file_path), 0); |
| // Check that the file is in our final location. |
| do_test_write_read_file(g_temp_file_path, false); |
| |
| // Demonstrates that symlinks cannot be renamed within the mounted directory. |
| ASSERT_EQ(rename(g_temp_symlink_path, new_file_location), -1); |
| ASSERT_EQ(errno, EACCES); |
| |
| passed("test_rename_access", "all"); |
| } |
| |
| void test_escape_attempt() { |
| // Try to escape the directory -- should not be able to do so. |
| |
| // Attempting to leave the root directory via ".." places the cwd at the root |
| // of the mounted directory. |
| ASSERT_EQ(chdir("/.."), 0); |
| char cwd[PATH_MAX]; |
| ASSERT_EQ(getcwd(cwd, PATH_MAX), cwd); |
| ASSERT_EQ(strcmp(cwd, "/"), 0); |
| |
| char inaccessible_path[PATH_MAX]; |
| // Cannot open files outside root. |
| snprintf(inaccessible_path, PATH_MAX, "../%s", g_temp_inaccessible_file_name); |
| ASSERT_EQ(open(inaccessible_path, O_RDWR, S_IRUSR | S_IWUSR), -1); |
| ASSERT_EQ(errno, ENOENT); |
| |
| snprintf(inaccessible_path, PATH_MAX, "/../%s", |
| g_temp_inaccessible_file_name); |
| ASSERT_EQ(open(inaccessible_path, O_RDWR, S_IRUSR | S_IWUSR), -1); |
| ASSERT_EQ(errno, ENOENT); |
| |
| // Cannot open directories outside root. |
| struct stat buf; |
| ASSERT_EQ(stat(".", &buf), 0); |
| const ino_t root_inode = buf.st_ino; |
| ASSERT_EQ(stat("/", &buf), 0); |
| ASSERT_EQ(root_inode, buf.st_ino); |
| ASSERT_EQ(stat("..", &buf), 0); |
| ASSERT_EQ(root_inode, buf.st_ino); |
| ASSERT_EQ(stat("/..", &buf), 0); |
| ASSERT_EQ(root_inode, buf.st_ino); |
| ASSERT_EQ(stat("//..", &buf), 0); |
| ASSERT_EQ(root_inode, buf.st_ino); |
| ASSERT_EQ(stat("/../", &buf), 0); |
| ASSERT_EQ(root_inode, buf.st_ino); |
| ASSERT_EQ(stat("/..//", &buf), 0); |
| ASSERT_EQ(root_inode, buf.st_ino); |
| ASSERT_EQ(stat("/.././//../..", &buf), 0); |
| ASSERT_EQ(root_inode, buf.st_ino); |
| |
| snprintf(inaccessible_path, PATH_MAX, "../%s", g_temp_inaccessible_dir_name); |
| ASSERT_EQ(opendir(inaccessible_path), NULL); |
| ASSERT_EQ(errno, ENOENT); |
| |
| passed("test_escape_attempt", "all"); |
| } |
| |
| void test_information_leak() { |
| // Try to determine the name of our mounted root. If possible, this |
| // information leak could also lead to discovering directories and files |
| // outside the mount point. |
| char path[PATH_MAX]; |
| struct stat buf; |
| |
| ASSERT_EQ_MSG(chdir("/"), 0, "chdir() failed"); |
| |
| // We should be able to access the root directory. |
| ASSERT_EQ(stat("/", &buf), 0); |
| const ino_t root_inode = buf.st_ino; |
| ASSERT_EQ(stat("//", &buf), 0); |
| ASSERT_EQ(root_inode, buf.st_ino); |
| ASSERT_EQ(stat("/./.", &buf), 0); |
| ASSERT_EQ(root_inode, buf.st_ino); |
| ASSERT_EQ(stat("/./////.", &buf), 0); |
| ASSERT_EQ(root_inode, buf.st_ino); |
| ASSERT_EQ(stat(".", &buf), 0); |
| ASSERT_EQ(root_inode, buf.st_ino); |
| |
| // '/..' should equal '/'. |
| ASSERT_EQ(stat("/..", &buf), 0); |
| ASSERT_EQ(root_inode, buf.st_ino); |
| |
| // We should not be able to identify our mount point this way. |
| snprintf(path, PATH_MAX, "/../%s", g_temp_dir_name); |
| ASSERT_EQ(stat(path, &buf), -1); |
| ASSERT_EQ(errno, ENOENT); |
| snprintf(path, PATH_MAX, "//../%s", g_temp_dir_name); |
| ASSERT_EQ(stat(path, &buf), -1); |
| ASSERT_EQ(errno, ENOENT); |
| snprintf(path, PATH_MAX, "/.//..//%s", g_temp_dir_name); |
| ASSERT_EQ(stat(path, &buf), -1); |
| ASSERT_EQ(errno, ENOENT); |
| snprintf(path, PATH_MAX, "../%s", g_temp_dir_name); |
| ASSERT_EQ(stat(path, &buf), -1); |
| ASSERT_EQ(errno, ENOENT); |
| snprintf(path, PATH_MAX, "%s/../..", g_temp_sub_dir_name); |
| ASSERT_EQ(stat(path, &buf), 0); |
| ASSERT_EQ(root_inode, buf.st_ino); |
| |
| passed("test_information_leak", "all"); |
| } |
| |
| void test_parent_directory_access() { |
| // We should be able to access valid paths using "..", as long as they are |
| // within our root directory. |
| char path[PATH_MAX]; |
| struct stat buf; |
| ASSERT_EQ_MSG(chdir("/"), 0, "chdir() failed"); |
| |
| ASSERT_EQ(stat("/", &buf), 0); |
| const ino_t root_inode = buf.st_ino; |
| snprintf(path, PATH_MAX, "%s", g_temp_sub_dir_path); |
| ASSERT_EQ(stat(path, &buf), 0); |
| const ino_t subdir_inode = buf.st_ino; |
| ASSERT_NE(root_inode, subdir_inode); |
| |
| // Test valid absolute accesses with ".." |
| snprintf(path, PATH_MAX, "%s/..", g_temp_sub_dir_path); |
| ASSERT_EQ(stat(path, &buf), 0); |
| ASSERT_EQ(root_inode, buf.st_ino); |
| snprintf(path, PATH_MAX, "%s/../.", g_temp_sub_dir_path); |
| ASSERT_EQ(stat(path, &buf), 0); |
| ASSERT_EQ(root_inode, buf.st_ino); |
| |
| // Test valid relative accesses with ".." |
| snprintf(path, PATH_MAX, "%s/../.", g_temp_sub_dir_name); |
| ASSERT_EQ(stat(path, &buf), 0); |
| ASSERT_EQ(root_inode, buf.st_ino); |
| snprintf(path, PATH_MAX, "./%s/../.", g_temp_sub_dir_name); |
| ASSERT_EQ(stat(path, &buf), 0); |
| ASSERT_EQ(root_inode, buf.st_ino); |
| |
| // Test invalid relative accesses with "..". |
| snprintf(path, PATH_MAX, "%s/file_does_not_exist/..", g_temp_sub_dir_name); |
| ASSERT_EQ(stat(path, &buf), -1); |
| ASSERT_EQ(errno, ENOENT); |
| |
| passed("test_parent_directory_access", "all"); |
| } |
| |
| void test_valid_file_access() { |
| // Show that reads and writes to valid files work. |
| char file_name[PATH_MAX]; |
| ASSERT_EQ_MSG(chdir("/"), 0, "chdir() failed"); |
| |
| // Absolute path |
| snprintf(file_name, PATH_MAX, "%s", g_temp_file_path); |
| do_test_write_read_file(file_name, /* new_file= */ false); |
| |
| // Relative path |
| snprintf(file_name, PATH_MAX, "%s", g_temp_file_name); |
| do_test_write_read_file(file_name, /* new_file= */ false); |
| |
| // Absolute path |
| snprintf(file_name, PATH_MAX, "%s/%s", g_temp_sub_dir_path, |
| g_temp_sub_file_name); |
| do_test_write_read_file(file_name, /* new_file= */ false); |
| |
| // Relative path |
| snprintf(file_name, PATH_MAX, "%s/%s", g_temp_sub_dir_name, |
| g_temp_sub_file_name); |
| do_test_write_read_file(file_name, /* new_file= */ false); |
| |
| ASSERT_EQ_MSG(chdir(g_temp_sub_dir_name), 0, "chdir() failed"); |
| |
| // Relative path |
| snprintf(file_name, PATH_MAX, "%s", g_temp_sub_file_name); |
| do_test_write_read_file(file_name, /* new_file= */ false); |
| |
| passed("test_valid_file_access", "all"); |
| } |
| |
| void test_new_file_access() { |
| // Create a new file, show that it is readable / writable. |
| char file_name[PATH_MAX]; |
| do_test_write_read_file("/new_temp_file", true); |
| |
| snprintf(file_name, PATH_MAX, "%s/newer_temp_file", g_temp_sub_dir_path); |
| do_test_write_read_file(file_name, true); |
| |
| snprintf(file_name, PATH_MAX, "%s/..newer_temp_file", g_temp_sub_dir_path); |
| do_test_write_read_file(file_name, true); |
| |
| passed("test_new_file_access", "all"); |
| } |
| |
| /* |
| * function testSuite() |
| * |
| * Run through a complete sequence of file tests. |
| */ |
| |
| void testSuite() { |
| test_directory_walk(); |
| test_new_directory_access(); |
| test_link_access(); |
| test_symlink_access(); |
| test_rename_access(); |
| test_escape_attempt(); |
| test_information_leak(); |
| test_parent_directory_access(); |
| test_valid_file_access(); |
| test_new_file_access(); |
| } |
| |
| } // anonymous namespace |
| |
| /* |
| * main entry point. |
| * |
| * run all tests and call system exit with appropriate value. |
| */ |
| int main(const int argc, const char *argv[]) { |
| if (argc != 8) { |
| printf("Unexpected arguments\n"); |
| exit(-1); |
| } |
| |
| snprintf(g_temp_dir_name, PATH_MAX, "%s", argv[1]); |
| snprintf(g_temp_dir_path, PATH_MAX, "/%s", argv[1]); |
| |
| snprintf(g_temp_file_name, PATH_MAX, "%s", argv[2]); |
| snprintf(g_temp_file_path, PATH_MAX, "/%s", argv[2]); |
| |
| snprintf(g_temp_symlink_name, PATH_MAX, "%s", argv[3]); |
| snprintf(g_temp_symlink_path, PATH_MAX, "/%s", argv[3]); |
| |
| snprintf(g_temp_sub_dir_name, PATH_MAX, "%s", argv[4]); |
| snprintf(g_temp_sub_dir_path, PATH_MAX, "/%s", argv[4]); |
| |
| snprintf(g_temp_sub_file_name, PATH_MAX, "%s", argv[5]); |
| snprintf(g_temp_sub_file_path, PATH_MAX, "/%s/%s", |
| g_temp_sub_dir_name, argv[5]); |
| |
| snprintf(g_temp_inaccessible_dir_name, PATH_MAX, "%s", argv[6]); |
| snprintf(g_temp_inaccessible_file_name, PATH_MAX, "%s", argv[7]); |
| |
| // Run the full test suite. |
| testSuite(); |
| printf("All tests PASSED\n"); |
| exit(0); |
| } |