Changeset View
Standalone View
lib/libcasper/services/cap_exec/cap_exec.c
- This file was added.
/*- | |||||
* SPDX-License-Identifier: BSD-2-Clause | |||||
* | |||||
* Copyright (c) 2020 The FreeBSD Foundation | |||||
* | |||||
* This software was developed by Tiger Gao under sponsorship from | |||||
* the FreeBSD Foundation. | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions are | |||||
* met: | |||||
* 1. Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* 2. Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in | |||||
* the documentation and/or other materials provided with the distribution. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND | |||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | |||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||||
* SUCH DAMAGE. | |||||
*/ | |||||
#include <sys/capsicum.h> | |||||
#include <sys/dnv.h> | |||||
#include <sys/errno.h> | |||||
#include <sys/event.h> | |||||
#include <sys/nv.h> | |||||
#include <sys/procdesc.h> | |||||
#include <sys/queue.h> | |||||
#include <stdbool.h> | |||||
#include <stdio.h> | |||||
#include <stdlib.h> | |||||
#include <string.h> | |||||
#include <libcasper.h> | |||||
#include <libcasper_service.h> | |||||
#include "cap_exec.h" | |||||
#include <assert.h> | |||||
#include <sys/wait.h> | |||||
#include <sys/types.h> | |||||
#include <err.h> | |||||
#define EXEC_MAGIC 0xEEC0EEC0 | |||||
struct PROCESS | |||||
{ | |||||
LIST_ENTRY(PROCESS) processes; | |||||
int fd, pid; | |||||
oshogbo: Multiple calls to this function will break the service.
For example in case when we have… | |||||
Not Done Inline ActionsI don't think this would be an issue. cap_exec_init has the same functionality as fileargs_init. It is only called once in the beginning of user program to initialize the set of allowed executables. tig_freebsdfoundation.org: I don't think this would be an issue. cap_exec_init has the same functionality as fileargs_init. | |||||
Not Done Inline ActionsYou can call fileargs_init multiple time, there is no global state on the user side. oshogbo: You can call fileargs_init multiple time, there is no global state on the user side.
Each time… | |||||
Not Done Inline ActionsI see that now - didn't consider the case where a casper channel can be passed to a different process. tig_freebsdfoundation.org: I see that now - didn't consider the case where a casper channel can be passed to a different… | |||||
Not Done Inline ActionsThat sounds right. Then, it would make sense move the other work into the service as well. yzhong_freebsdfoundation.org: That sounds right. Then, it would make sense move the other work into the service as well. | |||||
Done Inline Actions
Could you tell me a bit about how fileargs handles state? Is it stored on the user side, or is it in the service side? To me, it appears to be stored on the user side, in the fileargs_t structures. yzhong_freebsdfoundation.org: > You can call fileargs_init multiple time, there is no global state on the user side.
> Each… | |||||
}; | |||||
struct cap_exec | |||||
{ | |||||
uint32_t exec_magic; | |||||
cap_channel_t *exec_chan; | |||||
LIST_HEAD(, PROCESS) exec_procs; | |||||
}; | |||||
cap_exec_t * | |||||
cap_exec_init(cap_channel_t *cas, int count, const char **arr) | |||||
{ | |||||
nvlist_t *allowed_programs; | |||||
cap_channel_t *chan; | |||||
cap_exec_t *exec; | |||||
assert(cas != NULL); | |||||
chan = cap_service_open(cas, "system.exec"); | |||||
if (chan == NULL) | |||||
return (NULL); | |||||
allowed_programs = nvlist_create(0); | |||||
for (int i = 0; i < count; ++i) | |||||
nvlist_add_null(allowed_programs, arr[i]); | |||||
if (cap_limit_set(chan, allowed_programs) < 0) | |||||
return (NULL); | |||||
exec = malloc(sizeof(cap_exec_t)); | |||||
if (exec != NULL) { | |||||
exec->exec_magic = EXEC_MAGIC; | |||||
exec->exec_chan = chan; | |||||
LIST_INIT(&exec->exec_procs); | |||||
} | |||||
return (exec); | |||||
} | |||||
void | |||||
cap_exec_free(cap_exec_t *exec) | |||||
{ | |||||
struct PROCESS *proc; | |||||
if (exec == NULL) | |||||
return; | |||||
assert (exec->exec_magic == EXEC_MAGIC); | |||||
while (!LIST_EMPTY(&exec->exec_procs)) { | |||||
proc = LIST_FIRST(&exec->exec_procs); | |||||
LIST_REMOVE(proc, processes); | |||||
Done Inline ActionsDo we want to do fdopen on -1, this won't override the errno set by us? oshogbo: Do we want to do fdopen on -1, this won't override the errno set by us? | |||||
free(proc); | |||||
} | |||||
if (exec->exec_chan != NULL) | |||||
cap_close(exec->exec_chan); | |||||
explicit_bzero(&exec->exec_magic, sizeof(exec->exec_magic)); | |||||
free(exec); | |||||
} | |||||
FILE * | |||||
cap_exec_open(cap_exec_t *exec, const char *command, const char *mode) | |||||
{ | |||||
nvlist_t *nvl; | |||||
int error; | |||||
int fd, pid; | |||||
assert(exec != NULL); | |||||
assert(exec->exec_magic == EXEC_MAGIC); | |||||
nvl = nvlist_create(0); | |||||
nvlist_add_string(nvl, "cmd", "open"); | |||||
nvlist_add_string(nvl, "command", command); | |||||
nvl = cap_xfer_nvlist(exec->exec_chan, nvl); | |||||
if (nvl == NULL) | |||||
return (NULL); | |||||
error = (int)dnvlist_get_number(nvl, "error", 0); | |||||
fd = dnvlist_take_descriptor(nvl, "filedesc", -1); | |||||
pid = dnvlist_take_number(nvl, "pid", -1); | |||||
nvlist_destroy(nvl); | |||||
if (error != 0) { | |||||
close(fd); | |||||
errno = error; | |||||
return (NULL); | |||||
} | |||||
struct PROCESS *item = malloc(sizeof(struct PROCESS)); | |||||
if (item == NULL) | |||||
return (NULL); | |||||
item->fd = fd; | |||||
item->pid = pid; | |||||
LIST_INSERT_HEAD(&exec->exec_procs, item, processes); | |||||
return (fdopen(fd, mode)); | |||||
} | |||||
int | |||||
Done Inline ActionsLikely you will need to change this since we now have two user interface functions cap_exec_open() and cap_exec_close() instead of just cap_exec() tig_freebsdfoundation.org: Likely you will need to change this since we now have two user interface functions… | |||||
cap_exec_close(cap_exec_t *exec, FILE *file) | |||||
{ | |||||
struct PROCESS *item, *temp; | |||||
nvlist_t *nvl; | |||||
int error, fd, pid; | |||||
fd = fileno(file); | |||||
pid = -1; | |||||
Done Inline ActionsIt is not a good idea to define dynamically sized buffers on the stack. In this case especially, the command string is being sent from an untrusted source and we have no size checking, and the compiler has no idea how much stack space the OS has allocated for us here. You can either malloc() a copy or try using nvlist_take_string(). The latter is destructive and removes the "command" value from nvlin, but I believe that's ok based on the fact that nvlin is not declared with const. markj: It is not a good idea to define dynamically sized buffers on the stack. In this case especially… | |||||
LIST_FOREACH_SAFE(item, &exec->exec_procs, processes, temp) { | |||||
if (item && (item->fd == fd)) { | |||||
pid = item->pid; | |||||
break; | |||||
} | |||||
} | |||||
if (pid == -1) { | |||||
errno = ESRCH; | |||||
return (-1); | |||||
} | |||||
nvl = nvlist_create(0); | |||||
nvlist_add_string(nvl, "cmd", "close"); | |||||
nvlist_add_descriptor(nvl, "filedesc", fd); | |||||
nvlist_add_number(nvl, "pid", pid); | |||||
fclose(file); | |||||
nvl = cap_xfer_nvlist(exec->exec_chan, nvl); | |||||
error = (int)dnvlist_get_number(nvl, "error", 0); | |||||
nvlist_destroy(nvl); | |||||
if (error != 0) { | |||||
errno = error; | |||||
return (-1); | |||||
} | |||||
return (0); | |||||
} | |||||
static int | |||||
exec_limits(const nvlist_t *oldlimits, const nvlist_t *newlimits) | |||||
{ | |||||
/* only allow limit to be set once */ | |||||
if (oldlimits != NULL) | |||||
return (ENOTCAPABLE); | |||||
(void) newlimits; | |||||
return (0); | |||||
} | |||||
static int | |||||
exec_command_open(const nvlist_t *limits, nvlist_t *nvlin, nvlist_t *nvlout) | |||||
{ | |||||
const char *command; | |||||
char *prog, *buf; | |||||
int fd[2]; | |||||
int pid; | |||||
bool allowed; | |||||
if (limits == NULL) | |||||
return (ENOTCAPABLE); | |||||
command = nvlist_get_string(nvlin, "command"); | |||||
/* parse executable */ | |||||
buf = malloc(sizeof(char)*(strlen(command) + 1)); | |||||
strcpy(buf, command); | |||||
prog = strtok(buf, " "); | |||||
/* Check if program in allowed set */ | |||||
allowed = nvlist_exists_null(limits, prog); | |||||
free(buf); | |||||
if (!allowed) | |||||
return (ENOTCAPABLE); | |||||
if (pipe(fd) == -1) | |||||
return (errno); | |||||
pid = fork(); | |||||
if (pid == -1) { | |||||
return (ENOTCAPABLE); | |||||
} else if (pid == 0) { | |||||
close(fd[1]); | |||||
dup2(fd[0], STDIN_FILENO); | |||||
dup2(fd[0], STDOUT_FILENO); | |||||
close(fd[0]); | |||||
execlp("sh", "sh", "-c", command, NULL); | |||||
} else { | |||||
close(fd[0]); | |||||
nvlist_move_descriptor(nvlout, "filedesc", fd[1]); | |||||
nvlist_add_number(nvlout, "pid", pid); | |||||
} | |||||
return (0); | |||||
} | |||||
static int | |||||
exec_command_close(const nvlist_t *limits, nvlist_t *nvlin) | |||||
{ | |||||
int fd, pid; | |||||
if (limits == NULL) | |||||
return (ENOTCAPABLE); | |||||
fd = nvlist_get_descriptor(nvlin, "filedesc"); | |||||
pid = nvlist_get_number(nvlin, "pid"); | |||||
close(fd); | |||||
waitpid(pid, NULL, 0); | |||||
return (0); | |||||
} | |||||
static int | |||||
exec_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin, | |||||
nvlist_t *nvlout) | |||||
{ | |||||
if (strcmp(cmd, "open") == 0) | |||||
return (exec_command_open(limits, nvlin, nvlout)); | |||||
if (strcmp(cmd, "close") == 0) | |||||
return (exec_command_close(limits, nvlin)); | |||||
return (EINVAL); | |||||
} | |||||
CREATE_SERVICE("system.exec", exec_limits, exec_command, 0); |
Multiple calls to this function will break the service.
For example in case when we have multiple exec channels.