Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mount a tmpfs at /dev/shm in the sandbox. #3263

Merged
merged 1 commit into from
Apr 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ NODE_HEADERS=$(METEOR_DEV_BUNDLE)/include/node
WARNINGS=-Wall -Wextra -Wglobal-constructors -Wno-sign-compare -Wno-unused-parameter
CXXFLAGS2=-std=c++1z $(WARNINGS) $(CXXFLAGS) -DSANDSTORM_BUILD=$(BUILD) -DKJ_HAS_OPENSSL -DKJ_HAS_ZLIB -DKJ_HAS_LIBDL -pthread -fPIC -I$(NODE_HEADERS) -DKJ_STD_COMPAT
CFLAGS2=$(CFLAGS) -pthread -fPIC -DKJ_STD_COMPAT
LIBS2=$(LIBS) deps/libsodium/build/src/libsodium/.libs/libsodium.a deps/boringssl/build/ssl/libssl.a deps/boringssl/build/crypto/libcrypto.a -lz -ldl -pthread
# -lrt is not used by sandstorm itself, but the test app uses it. It would be
# nice if we could not link everything against it.
LIBS2=$(LIBS) deps/libsodium/build/src/libsodium/.libs/libsodium.a deps/boringssl/build/ssl/libssl.a deps/boringssl/build/crypto/libcrypto.a -lz -ldl -pthread -lrt

define color
printf '\033[0;34m==== $1 ====\033[0m\n'
Expand Down
21 changes: 19 additions & 2 deletions src/sandstorm/supervisor.c++
Original file line number Diff line number Diff line change
Expand Up @@ -954,6 +954,12 @@ void SupervisorMain::makeCharDeviceNode(
KJ_SYSCALL(mount(kj::str("/dev/", realName).cStr(), dst.cStr(), nullptr, MS_BIND, nullptr));
}

void mountTmpFs(const char *name, const char *dest) {
KJ_SYSCALL(mount(name, dest, "tmpfs",
MS_NOSUID | MS_NODEV,
"size=16m,nr_inodes=4k,mode=770"));
}

void SupervisorMain::setupFilesystem() {
// The root of our mount namespace will be the app package itself. We optionally create
// tmp, dev, and var. tmp is an ordinary tmpfs. dev is a read-only tmpfs that contains
Expand Down Expand Up @@ -986,8 +992,7 @@ void SupervisorMain::setupFilesystem() {
// 2) When we exit, the mount namespace disappears and the tmpfs is thus automatically
// unmounted. No need for careful cleanup, and no need to implement a risky recursive
// delete.
KJ_SYSCALL(mount("sandstorm-tmp", "tmp", "tmpfs", MS_NOSUID,
"size=16m,nr_inodes=4k,mode=770"));
mountTmpFs("sandstorm-tmp", "tmp");
}
if (access("dev", F_OK) == 0) {
KJ_SYSCALL(mount("sandstorm-dev", "dev", "tmpfs",
Expand All @@ -997,6 +1002,18 @@ void SupervisorMain::setupFilesystem() {
makeCharDeviceNode("zero", "zero", 1, 5);
makeCharDeviceNode("random", "urandom", 1, 9);
makeCharDeviceNode("urandom", "urandom", 1, 9);

// Create /dev/shm so shm_open() and friends work. Note that even though /dev
// is already a tmpfs, we need to mount a separate tmpfs for /dev/shm, because
// the former will be read-only.
//
// TODO: it might be nice to have /dev/shm and /tmp share the same partition,
// so we don't have to strictly separate their storage capacity. We could mount
// a single tmpfs somewhere invisible, create subdirectories, and then bind-mount
// them to their final destinations.
KJ_SYSCALL(mkdir("dev/shm", 0700));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the /dev filesystem itself is created with 0755 permission, any particular reason to use 0700 here? I guess it ultimately doesn't mater since there's only one user in the sandbox and they own everything?

mountTmpFs("sandstorm-shm", "dev/shm");

KJ_SYSCALL(mount("dev", "dev", nullptr,
MS_REMOUNT | MS_BIND | MS_NOEXEC | MS_NOSUID | MS_NODEV | MS_RDONLY,
nullptr));
Expand Down
40 changes: 40 additions & 0 deletions src/sandstorm/test-app/test-app.c++
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
#include <map>

#include <sys/time.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include <kj/main.h>
#include <kj/debug.h>
Expand Down Expand Up @@ -93,6 +97,39 @@ private:

// =======================================================================================

void testSystemApi() {
// Test that some syscalls & platform APIs work as expected. Print a success
// message to the console so the test suite can verify this.

std::cout << "Testing System APIs" << std::endl;

auto result = kj::runCatchingExceptions([]() {
// Test use of /dev/shm:
const char *obj_name = "/some-shm-obj";
int shm_fd;
KJ_SYSCALL(shm_fd = shm_open(obj_name, O_RDWR|O_CREAT, 0700));
KJ_DEFER(KJ_SYSCALL(shm_unlink(obj_name)));

// Make sure the mapping actually works:
int *mapped = (int *)mmap(
nullptr, sizeof(int), PROT_READ|PROT_WRITE, MAP_SHARED, shm_fd, 0
);
KJ_ASSERT(mapped != MAP_FAILED, "mmap() failed");
KJ_ASSERT(close(shm_fd) == 0, "Closing shm_fd failed");
KJ_SYSCALL(munmap(mapped, sizeof(int)));
});

KJ_IF_MAYBE(exception, result) {
auto msg = kj::str(*exception);
std::cout << msg.cStr() << std::endl;
throw(*exception);
}

std::cout << "testSystemApi() passed." << std::endl;
}

// =======================================================================================

class WebSessionImpl final: public sandstorm::WebSession::Server {
public:
WebSessionImpl(sandstorm::UserInfo::Reader userInfo,
Expand Down Expand Up @@ -148,6 +185,9 @@ public:
httpResponse.setMimeType("text/plain");
httpResponse.getBody().setBytes(response.getText().asBytes());
});
} else if(path == "test-system-api") {
testSystemApi();
return kj::READY_NOW;
} else if(path == "schedule") {
context.getResults().initNoContent();
// Put the extra headers in a map, so we can easily look for specific ones:
Expand Down
2 changes: 2 additions & 0 deletions src/sandstorm/test-app/test-app.html
Original file line number Diff line number Diff line change
Expand Up @@ -155,5 +155,7 @@ <h1>Test App</h1>

<p><button onclick="postScheduledJob({shouldCancel: false, refStr: 'oneshot'})" id="do-schedule-oneshot">
Schedule a one-shot job.</button></p>

<p><button onclick="doPost('/test-system-api', '')" id="do-test-system-api">Test the system api.</button></p>
</body>
</html>
47 changes: 47 additions & 0 deletions tests/tests/system-api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Sandstorm - Personal Cloud Sandbox
// Copyright (c) 2020 Sandstorm Development Group, Inc. and contributors
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

"use strict";

const { short_wait } = require('../utils');

module.exports = {};

module.exports["Test system api"] = function(browser) {
const selector = "#do-test-system-api";
browser
.init()
.loginDevAccount()
.uploadTestApp()
.assert.containsText("#grainTitle", "Untitled Sandstorm Test App instance")
// Start opening this now, so we don't have to wait for it later when we
// want to use it:
.click("#openDebugLog")
.grainFrame()
.waitForElementPresent(selector, short_wait)
.click(selector)
.pause(short_wait)
.windowHandles(windows => browser.switchWindow(windows.value[1]))
.waitForElementVisible(".grainlog-contents > pre", short_wait)
.assert.containsText(".grainlog-contents > pre", "testSystemApi() passed.")

// Close the grain log, and switch back to to the main window, to avoid
// confusing future tests:
browser.windowHandles(windows => {
browser.closeWindow()
browser.switchWindow(windows.value[0])
})
}