blob: e8e4629ef0567740ddfaed31962799dcd2e249b1 [file] [log] [blame]
/*
* 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);
}