/* vim: set expandtab ts=4 sw=4: */
/*
* You may redistribute this program and/or modify it under the terms of
* the GNU General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "exception/Except.h"
#include "util/log/Log.h"
#include "util/Security.h"
#include "util/Seccomp.h"
#include "memory/Allocator.h"
#include
#include
#include
#include
#include
#include
#include
#define __USE_MISC // for MAP_ANONYMOUS
#include
#include
static const unsigned long cfgMaxMemoryBytes = 100000000;
int Security_setUser(char* userName, struct Log* logger, struct Except* eh)
{
struct passwd* pw = getpwnam(userName);
if (!pw) {
Except_throw(eh, "Failed to set UID, couldn't find user named [%s].",
strerror(errno));
}
if (setuid(pw->pw_uid)) {
if (errno == EPERM) {
return Security_setUser_PERMISSION;
}
Except_throw(eh, "Failed to set UID [%s]", strerror(errno));
}
if (getuid() != pw->pw_uid) {
Except_throw(eh, "Failed to set UID but seemed to succeed");
}
return 0;
}
static int canOpenFiles()
{
int file = dup(0);
close(file);
return file >= 0;
}
static void noFiles(struct Except* eh)
{
#if !defined(RLIMIT_NOFILE) && defined(RLIMIT_OFILE)
#define RLIMIT_NOFILE RLIMIT_OFILE
#endif
if (!canOpenFiles()) {
Except_throw(eh, "Unable to dupe stdin");
}
if (setrlimit(RLIMIT_NOFILE, &(struct rlimit){ 0, 0 })) {
Except_throw(eh, "Failed to set open file limit to [%s]", strerror(errno));
}
if (canOpenFiles()) {
Except_throw(eh, "Still able to dupe stdin after setting number of files to 0!");
}
}
// RLIMIT_DATA doesn't prevent malloc() on linux.
// see: http://lkml.indiana.edu/hypermail/linux/kernel/0707.1/0675.html
#if !defined(RLIMIT_AS) && defined(RLIMIT_DATA)
#define Security_MEMORY_RLIMIT RLIMIT_DATA
#elif defined(RLIMIT_AS)
#define Security_MEMORY_RLIMIT RLIMIT_AS
#else
#error RLIMIT_AS and RLIMIT_DATA are not defined
#endif
static unsigned long getReportedMaxMemory(struct Except* eh)
{
// Just report the reported maximum allowed memory.
// If no limit, returns 0;
struct rlimit lim = { 0, 0 };
if (getrlimit(Security_MEMORY_RLIMIT, &lim)) {
Except_throw(eh, "Failed to get memory limit [%s]", strerror(errno));
}
#if defined(RLIM_INFINITY)
if (lim.rlim_max == RLIM_INFINITY) {
return 0;
}
#endif // RLIM_INFINITY
if (lim.rlim_max + 1 < lim.rlim_max) { // Systems without RLIM_INFINITY.
return 0;
}
return lim.rlim_max;
}
static unsigned long getMaxMemory(struct Except* eh)
{ /* Determine the amount of memory allowed to process. */
unsigned long reportedMemory = getReportedMaxMemory(eh);
// First time around, we try a very small mapping just to make sure it works.
size_t tryMapping = 100;
if (reportedMemory > 0) {
tryMapping = reportedMemory * 2l;
}
// Apple doesn't handle MAP_ANON | MAP_PRIVATE for some (unknown) reason.
// And apple doesn't have MAP_ANONYMOUS, only MAP_ANON.
#ifdef darwin
#define FLAGS MAP_ANON
#elif openbsd
#define FLAGS MAP_PRIVATE | MAP_ANON
#else
#define FLAGS MAP_PRIVATE | MAP_ANONYMOUS
#endif
void* ptr = mmap(NULL, tryMapping, PROT_READ | PROT_WRITE, FLAGS, -1, 0);
if (ptr != MAP_FAILED) {
munmap(ptr, tryMapping);
if (reportedMemory > 0) {
Except_throw(eh, "Memory limit is not enforced, successfully mapped [%zu] bytes, "
"while limit is [%lu] bytes", tryMapping, reportedMemory);
}
} else if (reportedMemory == 0) {
Except_throw(eh, "Testing of memory limit not possible, unable to map memory [%s]",
strerror(errno));
}
return reportedMemory;
}
static void setMaxMemory(unsigned long max, struct Except* eh)
{
unsigned long realMax = getReportedMaxMemory(eh);
if (realMax > 0 && realMax < max) {
Except_throw(eh, "Failed to limit available memory to [%lu] "
"because existing limit is [%lu]", max, realMax);
}
if (setrlimit(Security_MEMORY_RLIMIT, &(struct rlimit){ max, max })) {
Except_throw(eh, "Failed to limit available memory [%s]", strerror(errno));
}
if (!setrlimit(Security_MEMORY_RLIMIT, &(struct rlimit){ max+1, max+1 })) {
Except_throw(eh, "Available memory was modifyable after limiting");
}
realMax = getMaxMemory(eh);
if (realMax != max) {
Except_throw(eh, "Limiting available memory failed");
}
}
struct Security_Permissions* Security_checkPermissions(struct Allocator* alloc, struct Except* eh)
{
struct Security_Permissions* out =
Allocator_calloc(alloc, sizeof(struct Security_Permissions), 1);
out->noOpenFiles = !canOpenFiles();
out->seccompExists = Seccomp_exists();
out->seccompEnforcing = Seccomp_isWorking();
out->memoryLimitBytes = getMaxMemory(eh);
return out;
}
void Security_dropPermissions(struct Allocator* tempAlloc, struct Log* logger, struct Except* eh)
{
setMaxMemory(cfgMaxMemoryBytes, eh);
noFiles(eh);
Seccomp_dropPermissions(tempAlloc, logger, eh);
}