[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