[lxc-devel] [lxd/master] forkexec: rework
brauner on Github
lxc-bot at linuxcontainers.org
Sat Mar 28 22:29:43 UTC 2020
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 524 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200328/ed76662b/attachment-0001.bin>
-------------- next part --------------
From 60ebd6b809b4b4c1172119101879ef4f133cea81 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Sat, 28 Mar 2020 23:27:08 +0100
Subject: [PATCH] forkexec: rework
This reworks our forkexec logic to switch from go because it's just not working
well with anything that gets close to any libc and their state assumptions.
Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
lxd/include/memory_utils.h | 11 +
lxd/instance/drivers/driver_lxc.go | 11 +-
lxd/main_forkexec.go | 323 +++++++++++++++++++----------
lxd/main_nsexec.go | 3 +
shared/util_linux_cgo.go | 20 ++
5 files changed, 249 insertions(+), 119 deletions(-)
diff --git a/lxd/include/memory_utils.h b/lxd/include/memory_utils.h
index 269b2b6b03..611b65dc10 100644
--- a/lxd/include/memory_utils.h
+++ b/lxd/include/memory_utils.h
@@ -51,4 +51,15 @@ static inline void free_disarm_function(void *ptr)
}
#define __do_free call_cleaner(free_disarm)
+static inline void free_string_list(char **list)
+{
+ if (list) {
+ for (int i = 0; list[i]; i++)
+ free(list[i]);
+ free_disarm(list);
+ }
+}
+define_cleanup_function(char **, free_string_list);
+#define __do_free_string_list call_cleaner(free_string_list)
+
#endif /* __LXC_MEMORY_UTILS_H */
diff --git a/lxd/instance/drivers/driver_lxc.go b/lxd/instance/drivers/driver_lxc.go
index b36fdaa77a..f42fdb5df8 100644
--- a/lxd/instance/drivers/driver_lxc.go
+++ b/lxd/instance/drivers/driver_lxc.go
@@ -5633,15 +5633,16 @@ func (c *lxc) Exec(req api.InstanceExecPost, stdin *os.File, stdout *os.File, st
}
wStatus.Close()
- attachedPid := -1
- if err := json.NewDecoder(rStatus).Decode(&attachedPid); err != nil {
- logger.Errorf("Failed to retrieve PID of executing child process: %s", err)
- return nil, err
+ attachedPid := shared.ReadPid(rStatus)
+ if attachedPid <= 0 {
+ logger.Errorf("Failed to convert PID of executing child process: %d | %d", attachedPid, -1)
+ return nil, fmt.Errorf("Failed to convert PID of executing child process")
}
+ logger.Debugf("Retrieved PID %d from executing child process", attachedPid)
instCmd := &lxcCmd{
cmd: &cmd,
- attachedChildPid: attachedPid,
+ attachedChildPid: int(attachedPid),
}
return instCmd, nil
diff --git a/lxd/main_forkexec.go b/lxd/main_forkexec.go
index f42278ea66..ec7b8f9c22 100644
--- a/lxd/main_forkexec.go
+++ b/lxd/main_forkexec.go
@@ -1,155 +1,250 @@
package main
import (
- "encoding/json"
"fmt"
- "os"
- "strconv"
- "strings"
"github.com/spf13/cobra"
- "golang.org/x/sys/unix"
- liblxc "gopkg.in/lxc/go-lxc.v2"
)
-type cmdForkexec struct {
- global *cmdGlobal
+/*
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE 1
+#endif
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include "include/memory_utils.h"
+#include <lxc/attach_options.h>
+#include <lxc/lxccontainer.h>
+
+extern char *advance_arg(bool required);
+
+static bool write_nointr(int fd, const void *buf, size_t count)
+{
+ ssize_t ret;
+
+ do {
+ ret = write(fd, buf, count);
+ } while (ret < 0 && errno == EINTR);
+
+ if (ret < 0)
+ return false;
+
+ return (size_t)ret == count;
}
-func (c *cmdForkexec) Command() *cobra.Command {
- // Main subcommand
- cmd := &cobra.Command{}
- cmd.Use = "forkexec <container name> <containers path> <config> <cwd> <uid> <gid> -- env [key=value...] -- cmd <args...>"
- cmd.Short = "Execute a task inside the container"
- cmd.Long = `Description:
- Execute a task inside the container
+define_cleanup_function(struct lxc_container *, lxc_container_put);
- This internal command is used to spawn a task inside the container and
- allow LXD to interact with it.
-`
- cmd.RunE = c.Run
- cmd.Hidden = true
+static int wait_for_pid_status_nointr(pid_t pid)
+{
+ int status, ret;
- return cmd
+again:
+ ret = waitpid(pid, &status, 0);
+ if (ret == -1) {
+ if (errno == EINTR)
+ goto again;
+
+ return -1;
+ }
+
+ if (ret != pid)
+ goto again;
+
+ return status;
}
-func (c *cmdForkexec) Run(cmd *cobra.Command, args []string) error {
- // Sanity checks
- if len(args) < 7 {
- cmd.Help()
+static char *must_copy_string(const char *entry)
+{
+ char *ret;
- if len(args) == 0 {
- return nil
- }
+ if (!entry)
+ return NULL;
- return fmt.Errorf("Missing required arguments")
- }
+ do {
+ ret = strdup(entry);
+ } while (!ret);
- // Only root should run this
- if os.Geteuid() != 0 {
- return fmt.Errorf("This must be run as root")
- }
+ return ret;
+}
- name := args[0]
- lxcpath := args[1]
- configPath := args[2]
- cwd := args[3]
- uid, err := strconv.ParseUint(args[4], 10, 32)
- if err != nil {
- return err
- }
+static void *must_realloc(void *orig, size_t sz)
+{
+ void *ret;
- gid, err := strconv.ParseUint(args[5], 10, 32)
- if err != nil {
- return err
- }
+ do {
+ ret = realloc(orig, sz);
+ } while (!ret);
- // Get the status
- fdStatus := os.NewFile(uintptr(6), "attachedPid")
- defer fdStatus.Close()
+ return ret;
+}
- // Load the container
- d, err := liblxc.NewContainer(name, lxcpath)
- if err != nil {
- return fmt.Errorf("Error initializing instance for start: %q", err)
- }
+static int append_null_to_list(void ***list)
+{
+ int newentry = 0;
+
+ if (*list)
+ for (; (*list)[newentry]; newentry++)
+ ;
+
+ *list = must_realloc(*list, (newentry + 2) * sizeof(void **));
+ (*list)[newentry + 1] = NULL;
+ return newentry;
+}
+
+static void must_append_string(char ***list, char *entry)
+{
+ int newentry;
+ char *copy;
- err = d.LoadConfigFile(configPath)
- if err != nil {
- return fmt.Errorf("Error opening startup config file: %q", err)
+ newentry = append_null_to_list((void ***)list);
+ copy = must_copy_string(entry);
+ (*list)[newentry] = copy;
+}
+
+static int __forkexec(void)
+{
+ __do_close int status_pipe = 6;
+ __do_free_string_list char **argvp = NULL, **envvp = NULL;
+ call_cleaner(lxc_container_put) struct lxc_container *c = NULL;
+ const char *config_path = NULL, *lxcpath = NULL, *name = NULL;
+ lxc_attach_options_t attach_options = LXC_ATTACH_OPTIONS_DEFAULT;
+ lxc_attach_command_t command = {
+ .program = NULL,
+ };
+ int ret;
+ pid_t pid;
+ uid_t uid;
+ gid_t gid;
+
+ if (geteuid() != 0) {
+ fprintf(stderr, "Error: forkexec requires root privileges\n");
+ return EXIT_FAILURE;
}
- // Setup attach arguments
- opts := liblxc.DefaultAttachOptions
- opts.ClearEnv = true
- opts.StdinFd = 3
- opts.StdoutFd = 4
- opts.StderrFd = 5
- opts.UID = int(uid)
- opts.GID = int(gid)
-
- // Parse the command line
- env := []string{}
- command := []string{}
-
- section := ""
- for _, arg := range args[6:] {
- // The "cmd" section must come last as it may contain a --
- if arg == "--" && section != "cmd" {
- section = ""
- continue
+ name = advance_arg(false);
+ if (name == NULL ||
+ (strcmp(name, "--help") == 0 ||
+ strcmp(name, "--version") == 0 || strcmp(name, "-h") == 0))
+ return 0;
+
+ lxcpath = advance_arg(true);
+ config_path = advance_arg(true);
+ attach_options.initial_cwd = advance_arg(true);
+ uid = atoi(advance_arg(true));
+ if (uid < 0)
+ uid = (uid_t) - 1;
+ gid = atoi(advance_arg(true));
+ if (gid < 0)
+ gid = (gid_t) - 1;
+
+ for (char *arg = NULL, *section = NULL; (arg = advance_arg(false)); ) {
+ if (!strcmp(arg, "--") && (!section || strcmp(section, "cmd"))) {
+ section = NULL;
+ continue;
}
- if section == "" {
- section = arg
- continue
+ if (!section) {
+ section = arg;
+ continue;
}
- if section == "env" {
- fields := strings.SplitN(arg, "=", 2)
- if len(fields) == 2 && fields[0] == "HOME" {
- opts.Cwd = fields[1]
- }
- env = append(env, arg)
- } else if section == "cmd" {
- command = append(command, arg)
+ if (!strcmp(section, "env")) {
+ if (!strncmp(arg, "HOME=", STRLITERALLEN("HOME=")))
+ attach_options.initial_cwd = arg + STRLITERALLEN("HOME=");
+ must_append_string(&envvp, arg);
+ } else if (!strcmp(section, "cmd")) {
+ must_append_string(&argvp, arg);
} else {
- return fmt.Errorf("Invalid exec section: %s", section)
+ fprintf(stderr, "Invalid exec section %s\n", section);
+ return EXIT_FAILURE;
}
}
- opts.Env = env
- if cwd != "" {
- opts.Cwd = cwd
+ if (!argvp || !*argvp) {
+ fprintf(stderr, "No command specified\n");
+ return EXIT_FAILURE;
}
- // Exec the command
- status, err := d.RunCommandNoWait(command, opts)
- if err != nil {
- return fmt.Errorf("Failed running command: %q", err)
+ c = lxc_container_new(name, lxcpath);
+ if (!c)
+ return EXIT_FAILURE;
+
+ c->clear_config(c);
+
+ if (!c->load_config(c, config_path))
+ return EXIT_FAILURE;
+
+ attach_options.env_policy = LXC_ATTACH_CLEAR_ENV;
+ attach_options.extra_env_vars = envvp;
+ attach_options.stdin_fd = 3;
+ attach_options.stdout_fd = 4;
+ attach_options.stderr_fd = 5;
+ attach_options.uid = uid;
+ attach_options.gid = gid;
+ command.program = argvp[0];
+ command.argv = argvp;
+
+ ret = c->attach(c, lxc_attach_run_command, &command, &attach_options, &pid);
+ if (ret < 0)
+ return EXIT_FAILURE;
+
+ if (!write_nointr(status_pipe, &pid, sizeof(pid))) {
+ // Kill the child just to be safe.
+ fprintf(stderr, "Failed to send pid %d of executing child to LXD. Killing child\n", pid);
+ kill(pid, SIGKILL);
}
- // Send the PID of the executing process.
- err = json.NewEncoder(fdStatus).Encode(status)
- if err != nil {
- return fmt.Errorf("Failed sending PID of executing command: %q", err)
- }
+ ret = wait_for_pid_status_nointr(pid);
+ if (ret < 0)
+ return EXIT_FAILURE;
- // Handle exit code
- var ws unix.WaitStatus
- wpid, err := unix.Wait4(status, &ws, 0, nil)
- if err != nil || wpid != status {
- return fmt.Errorf("Failed finding process: %q", err)
- }
+ if (WIFEXITED(ret))
+ return WEXITSTATUS(ret);
- if ws.Exited() {
- os.Exit(ws.ExitStatus())
- }
+ if (WIFSIGNALED(ret))
+ return 128 + WTERMSIG(ret);
- if ws.Signaled() {
- // 128 + n == Fatal error signal "n"
- os.Exit(128 + int(ws.Signal()))
- }
+ return EXIT_FAILURE;
+}
+
+void forkexec(void)
+{
+ _exit(__forkexec());
+}
+*/
+import "C"
+
+type cmdForkexec struct {
+ global *cmdGlobal
+}
- return fmt.Errorf("Command failed")
+func (c *cmdForkexec) Command() *cobra.Command {
+ // Main subcommand
+ cmd := &cobra.Command{}
+ cmd.Use = "forkexec <container name> <containers path> <config> <cwd> <uid> <gid> -- env [key=value...] -- cmd <args...>"
+ cmd.Short = "Execute a task inside the container"
+ cmd.Long = `Description:
+ Execute a task inside the container
+
+ This internal command is used to spawn a task inside the container and
+ allow LXD to interact with it.
+`
+ cmd.RunE = c.Run
+ cmd.Hidden = true
+
+ return cmd
+}
+
+func (c *cmdForkexec) Run(cmd *cobra.Command, args []string) error {
+ return fmt.Errorf("This command should have been intercepted in cgo")
}
diff --git a/lxd/main_nsexec.go b/lxd/main_nsexec.go
index 9efc3af825..9568df2031 100644
--- a/lxd/main_nsexec.go
+++ b/lxd/main_nsexec.go
@@ -40,6 +40,7 @@ package main
// External functions
extern void checkfeature();
+extern void forkexec();
extern void forkfile();
extern void forksyscall();
extern void forkmount();
@@ -302,6 +303,8 @@ __attribute__((constructor)) void init(void) {
}
// Intercepts some subcommands
+ if (strcmp(cmdline_cur, "forkexec") == 0)
+ forkexec();
if (strcmp(cmdline_cur, "forkfile") == 0)
forkfile();
else if (strcmp(cmdline_cur, "forksyscall") == 0)
diff --git a/shared/util_linux_cgo.go b/shared/util_linux_cgo.go
index 69c9be5534..bb37c1f33a 100644
--- a/shared/util_linux_cgo.go
+++ b/shared/util_linux_cgo.go
@@ -119,6 +119,22 @@ again:
return ret;
}
+
+static int read_pid(int fd)
+{
+ ssize_t ret;
+ pid_t n = -1;
+
+again:
+ ret = read(fd, &n, sizeof(n));
+ if (ret < 0 && errno == EINTR)
+ goto again;
+
+ if (ret < 0)
+ return -1;
+
+ return n;
+}
*/
import "C"
@@ -439,3 +455,7 @@ func ExecReaderToChannel(r io.Reader, bufferSize int, exited <-chan struct{}, fd
return ch
}
+
+func ReadPid(r *os.File) int {
+ return int(C.read_pid(C.int(r.Fd())))
+}
More information about the lxc-devel
mailing list