Skip to content

Commit

Permalink
system.c: Support retaining network connectivity in container.
Browse files Browse the repository at this point in the history
The existing isoexec option uses CLONE_NEWNET, so there is no
network connectivity inside the container. While ideal for some
applications, this currently forces use of the plain "exec"
handler for anything requiring the network, which is not ideal.

This adds the ability to retain network connectivity by omitting
the CLONE_NEWNET flag, and adds a new handler, "isonetexec",
that can be used for this purpose. The isoroot program also
supports the -n option to keep network, useful for installing
packages or maintenance activities.

While the new isonetexec handler can still be dangerous, since
it can allow unrestricted network connections (it is not filtered
in any way), it is still far safer than using the plain "exec"
handler, so this should give users some much-needed flexibility.

Additionally:

* We now also change directories to a user's directory when
  launching a container, which is a more reasonable place
  for programs to get launched.
* The "Press a key" prompt after running external programs
  using the exec, isoexec (and now isonetexec) handlers is
  suppressed if the program exited with a 0 return code.
  The prompt still appears if the program exited nonzero.
  • Loading branch information
InterLinked1 committed Mar 22, 2024
1 parent 72ac1e1 commit e799e13
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 23 deletions.
30 changes: 29 additions & 1 deletion bbs/system.c
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,11 @@ int bbs_execvp_isolated(struct bbs_node *node, const char *filename, char *const
return __bbs_execvpe_fd(node, 1, -1, -1, filename, argv, NULL, 1);
}

int bbs_execvp_isolated_networked(struct bbs_node *node, const char *filename, char *const argv[])
{
return __bbs_execvpe_fd(node, 1, -1, -1, filename, argv, NULL, 2);
}

int bbs_execvp_headless(struct bbs_node *node, const char *filename, char *const argv[])
{
if (!node) {
Expand Down Expand Up @@ -720,6 +725,19 @@ static ssize_t full_read(int fd, char *restrict buf, size_t len)
}
#endif /* ISOEXEC_SUPPORTED */

/*!
* \brief Execute an external program. Most calls to exec() should funnel through this function...
* \param node
* \param usenode
* \param fdin
* \param fdout
* \param filename Program name to execute
* \param argv Arguments
* \param envp Environment (optional)
* \param isolated Isolation level. 0 = no isolation. 1 = isolated in separate namespace, no network. 2 = isolated in separate namespace, sharing host network
* \retval -1 on failure
* \return Result of program execution
*/
static int __bbs_execvpe_fd(struct bbs_node *node, int usenode, int fdin, int fdout, const char *filename, char *const argv[], char *const envp[], int isolated)
{
pid_t pid;
Expand Down Expand Up @@ -798,7 +816,10 @@ static int __bbs_execvpe_fd(struct bbs_node *node, int usenode, int fdin, int fd
int flags = 0;
/* We need to do more than fork() allows */
flags |= SIGCHLD | CLONE_NEWIPC | CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWNET | CLONE_NEWUSER; /* fork() sets SIGCHLD implicitly. */

if (isolated == 2) {
/* Keep network connectivity */
flags &= ~CLONE_NEWNET;
}
#if 0
flags |= CLONE_CLEAR_SIGHAND; /* Don't inherit any signals from parent. */
#else
Expand Down Expand Up @@ -1003,6 +1024,13 @@ static int __bbs_execvpe_fd(struct bbs_node *node, int usenode, int fdin, int fd
* an inside the container, rm -rf .old errors with "Device or resource busy". */
rmdir(oldroot); /* There is an empty /.old left behind, get rid of it as it's not needed anymore */

/* Shells will automatically default to our home directory,
* but other programs may not. Move to that directory now, if defined. */
if (myenvp[3]) {
const char *startdir = myenvp[3] + STRLEN("HOME=");
SYSCALL_OR_DIE(chdir, startdir);
}

if (node && envp == myenvp && display_motd) {
FILE *fp;
/* cd to the home directory; this way, if this is launching a shell session,
Expand Down
32 changes: 29 additions & 3 deletions configs/menus.conf
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,48 @@ title = Welcome to the BBS! ; Title to print on first line of screen.
; This is particularly useful on slow connections or small displays since you don't have to look
; at the intervening menus or even render them.

; Each menu item has a "handler" that is responsible for handling that menu option, if selected. Valid handlers are:
; menu - Recursively display another menu. The maximum menu depth is 12.
; return - Return to the previous menu
; quit - Quit the BBS, with confirmation prompt
; fastquit - Quit the BBS, without confirmation
; door - Execute a door module. These modules are in the "doors" directory of the BBS source tree.
; For modularity, most functionality in the BBS is implemented as a "door" module, even if internal to LBBS.
; file - Display a text file using the builtin file pager.
; exec - Execute a program, without any isolation from the underlying system. ABSOLUTELY DO NOT USE WITH UNTRUSTED PROGRAMS!
; isoexec - Execute a program in a sandboxed container. This is the RECOMMENDED way of executing external programs, when possible,
; since their execution is isolated from the rest of your system as much as possible. The container is run in its own
; namespace. This option is the most secure, as it does not support network connectivity, ideal for programs like shells,
; text editors, file managers, and any programs that can run fully offline.
; Most directories inside the container aside from /home, /tmp, and a few others are mounted read only from the
; container template, to conserve disk space.
; The filepath to isoexec is NOT the path on the host system; it is the path inside of the container. You will thus need
; to ensure the program binary is in the container prior to attempting to run it (you can use external/isoroot to administer the container).
; and that any libraries or runtime dependencies required are also present in the container.
; Currently, this handler is only supported on Linux (BSD not supported), and requires further configuration in system.conf.
; isonetexec - Same as isoexec, but retain network connectivity through the host. Note that even though this still runs inside a container,
; it may still be dangerous for some applications, as the network access is unrestricted. This would allow users with the ability to establish
; network connections to perform arbitrary network operations, e.g. sending spam email to other mail servers.
; Currently, there is no option to restrict network access, it is all or nothing, so this handler is required if any network connectivity is required.
; However, this is still safer than using "exec".

; Menus are drawn automatically (unless the display option is used) based on screen size and menu options, in the order listed below.
; Menus can also execute other menus, so you can have layers of menus and submenus. Syntax is menu:<menu name>
;M = menu:main|Main Menu ; in fact, you can even recurse into the same menu over and over again,
; but you should NOT do this because you will blow the menu stack.
C = door:chat:general|Chat ; Execute the "chat" door with argument "general" (name of the channel, in this case)
M = door:mail|Mail|minpriv=1 ; Launch the evergreen mail client (recommended for LBBS)
U = menu:utilities|${COLOR_RED}Utilities${COLOR_NONE} ; you can also specify the colors to use. Colors are global variables
;R = file:/home/bbs/announcements.txt|Read Announcements ; the file handler allows navigating a file from the terminal using the BBS file viewer.
W = door:listusers:active|Who's Online ; List all users that are online currently
L = door:listusers|List Users ; List all users on the BBS
N = door:listnodes|List Nodes ; List all nodes
M = door:listnodes:${BBS_USERNAME}|My Sessions|minpriv=1 ; List a user's sessions
S = door:listnodes:${BBS_USERNAME}|My Sessions|minpriv=1 ; List a user's sessions
? = door:tutorial|Help ; Launch the new user tutorial. You can also replace this with your own help system.
0 = door:msg|Msg Sysop ; Contact the sysop
; These are some special "builtin" menu handlers that menus can handle:
G = quit|Goodbye (Logoff) ; prompts confirmation to quit
F = fastquit|Goodbye Fast ; no confirmation to quit
Q = fastquit|Goodbye Fast ; no confirmation to quit

[games]
;artfile = /home/bbs/art.ANS ; ANSI art file to display, for ANSI terminals. Will be displayed before the first iteration of a menu execution.
Expand All @@ -43,7 +69,7 @@ title = Shall we play a game, ${BBS_USERNAME}?
; WARNING: The use of tabs (${TAB}) may lead to formatting inconsistencies. Spaces provided stronger formatting guarantees.
;display = ${COLOR_BLUE}M${COLOR_GREEN} Mail ${TAB}${TAB}${BBS_TIME} ; you can manually generate the menu screen yourself, rather than dynamically.
;display = ${COLOR_BROWN}Q${COLOR_GREEN} ${TAB} Return ; Just put each line on a separate display= directive line.
M = exec:mymailprog ${BBS_USERNAME}|Mail ; Here, the M option executes the "mymailprog" with the user's username as an argument.
;M = exec:mymailprog ${BBS_USERNAME}|Mail ; Here, the M option executes the "mymailprog" with the user's username as an argument.
Q = return|Back ; return back to previous menu, or quit if no menus left on the stack

[utilities]
Expand Down
57 changes: 44 additions & 13 deletions external/isoroot.c
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,33 @@ static int setup_namespace(pid_t pid)
close(map_pipe[0]);
return 0;
}

static int option_keep_network = 0;

static int parse_options(int argc, char *argv[])
{
static const char *getopt_settings = "hn?";
int c;

while ((c = getopt(argc, argv, getopt_settings)) != -1) {
switch (c) {
case 'h':
case '?':
fprintf(stderr, "Usage: isoroot [-options] [rootfs parent dir]\n");
fprintf(stderr, " -h Show this help\n");
fprintf(stderr, " -n Keep network connectivity inside the container\n");
return -1;
case 'n':
option_keep_network = 1;
break;
default:
fprintf(stderr, "Unknown option: %c\n", c);
return -1;
}
}

return 0;
}
#endif

int main(int argc, char *argv[])
Expand All @@ -200,28 +227,32 @@ int main(int argc, char *argv[])
fprintf(stderr, "This program only works on Linux\n");
return -1;
#else
int flags = SIGCHLD | CLONE_NEWIPC | CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWNET | CLONE_NEWUSER;
pid_t child;

if (pipe(map_pipe)) {
fprintf(stderr, "pipe failed: %s\n", strerror(errno));
if (parse_options(argc, argv)) {
return -1;
}

if (argc > 1) {
if (!strncmp(argv[1], "-h", 1) || !strncmp(argv[1], "-?", 1) || !strncmp(argv[1], "--help", 6)) {
fprintf(stderr, "Usage: isoroot [rootfs parent dir] [runuser]\n");
return -1;
}
if (optind < argc) {
/* Set working directory */
fprintf(stderr, "Using container root '%s/rootfs'\n", argv[1]);
if (chdir(argv[1])) {
fprintf(stderr, "Failed to change directories to %s: %s\n", argv[1], strerror(errno));
fprintf(stderr, "Using container root '%s/rootfs'\n", argv[optind]);
if (chdir(argv[optind])) {
fprintf(stderr, "Failed to change directories to %s: %s\n", argv[optind], strerror(errno));
_exit(errno);
}
}

child = clone(child_exec, child_stack + STACK_SIZE,
SIGCHLD | CLONE_NEWIPC | CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWNET | CLONE_NEWUSER, NULL);
if (option_keep_network) {
fprintf(stderr, "Retaining network connectivity...\n");
flags &= ~CLONE_NEWNET;
}

if (pipe(map_pipe)) {
fprintf(stderr, "pipe failed: %s\n", strerror(errno));
return -1;
}

child = clone(child_exec, child_stack + STACK_SIZE, flags, NULL);
if (child < 0) {
fprintf(stderr, "clone failed: %s\n", strerror(errno));
exit(errno);
Expand Down
3 changes: 3 additions & 0 deletions include/system.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ int bbs_execvp(struct bbs_node *node, const char *filename, char *const argv[]);
*/
int bbs_execvp_isolated(struct bbs_node *node, const char *filename, char *const argv[]);

/*! \brief Same as bbs_execvp_isolated, but retains network connectivity through the host, inside the container */
int bbs_execvp_isolated_networked(struct bbs_node *node, const char *filename, char *const argv[]);

/*! \brief Same as bbs_execvp, but node will not be used for I/O. */
int bbs_execvp_headless(struct bbs_node *node, const char *filename, char *const argv[]);

Expand Down
33 changes: 27 additions & 6 deletions modules/mod_handlers.c
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,20 @@ static int __exec_handler(struct bbs_node *node, char *args, int isolated)

bbs_node_clear_screen(node);
bbs_node_buffer(node); /* Assume that exec'd processes will want the terminal to be buffered: in canonical mode with echo on. */
if (isolated) {
res = bbs_execvp_isolated(node, argv[0], argv); /* Prog name is simply argv[0]. */
} else {
res = bbs_execvp(node, argv[0], argv); /* Prog name is simply argv[0]. */
/* Prog name is simply argv[0]. */
switch (isolated) {
case 2:
res = bbs_execvp_isolated_networked(node, argv[0], argv);
break;
case 1:
res = bbs_execvp_isolated(node, argv[0], argv);
break;
default:
bbs_warning("Invalid value %d\n", isolated);
/* Fall through */
case 0:
res = bbs_execvp(node, argv[0], argv);
break;
}
if (res < 0) {
return res;
Expand All @@ -125,8 +135,12 @@ static int __exec_handler(struct bbs_node *node, char *args, int isolated)
*/
return -1;
}
/* Who knows what this external program did. Prompt the user for confirmation before returning to menu. */
/* bbs_node_wait_key's unbuffer ill always succeed, regardless of actual current state, because as far as the BBS is concerned, we're buffered */
/* Who knows what this external program did. Prompt the user for confirmation before returning to menu, if the program exited nonzero. */
/* bbs_node_wait_key's unbuffer will always succeed, regardless of actual current state, because as far as the BBS is concerned, we're buffered */
bbs_node_unbuffer(node);
if (!res) {
return 0;
}
return bbs_node_wait_key(node, MIN_MS(2));
}

Expand All @@ -142,6 +156,12 @@ static int iso_exec_handler(struct bbs_node *node, char *args)
return __exec_handler(node, args, 1);
}

/*! \brief Execute a system command / program in an isolated environment, but sharing host networking */
static int isonet_exec_handler(struct bbs_node *node, char *args)
{
return __exec_handler(node, args, 2);
}

static int file_handler(struct bbs_node *node, char *args)
{
return bbs_node_term_browse(node, args);
Expand All @@ -159,6 +179,7 @@ static struct menu_handlers {
{ "door", door_handler, 1 },
{ "exec", exec_handler, 1 },
{ "isoexec", iso_exec_handler, 1 },
{ "isonetexec", isonet_exec_handler, 1 },
{ "file", file_handler, 1 },
};

Expand Down

0 comments on commit e799e13

Please sign in to comment.