Prevent child process from inheriting parent process's opened TCP ports with boost process library

1.2k Views Asked by At

I have a C++ application listening on a particular TCP port. The application also launches a child process using the Boost child class from Boost process library. As soon as the child is launched the output of netstat command shows that the TCP port also gets associated with the created child process. Is there a way by which I can prevent the child from inheriting the parent's ports when using boost process library ? The child is created as :

bp::child* proc = new bp::child("a.out", bp::std_out > stdout, bp::std_err > stderr); I am using Linux platform. Thanks.

1

There are 1 best solutions below

1
On

There's currently no way to do it.

I've toyed with a patch to add it, but I've had some problems and ran out of time testing things. I can share the patch, but you'll be on your own testing it.

Note everything below assumes POSIX systems.

Simple Take #1

Simplest thing to do would seem to be closing all non-standard fds, without exceptions:

struct close_fds : bp::extend::handler {
    template <typename Executor>
    void on_exec_setup(Executor& /*ex*/) {
        // TODO implemented smarter - below meddles with the library internals
        int maxfd=sysconf(_SC_OPEN_MAX);
        for(int fd=3; fd<maxfd; fd++) {
            ::close(fd);
        }
    }
};

Now you'd simply pass that keyword into your process creation API:

bp::child x(..., close_fds{});

THIS WON'T WORK

For one thing, there's the mandatory parent-child pipe used for internal communication within the Boost Process internals (e.g. error handling/reporting). We didn't account for it, and it wouldn't be predictable what fd value is involved, so let's move to more sophisticated ideas:

More Complicated Setups

More intelligent setups would take any fds into account that are involved by Boost Process. This includes the pipe (mentioned above) and any other fds that might be the result of redirections specified by the other process launch arguments.

Here's the patch as I had it prepared

Again, this is untested. The patch was originally against Boost 1.63 which didn't yet have the official Boost Process release, but I "recently" (April) ported it to Boost 1.66.

Usage would look similar to above:

bp::child x(..., bp::posix::fd.restrict_inherit()); };

Note that it allows coordination with other (custom) extensions to collect FDs that are supposed to be inherited.

From 45c46a3d9ed42278af97e6ca11474bfbddf3ffb4 Mon Sep 17 00:00:00 2001
From: Seth Heeren <[email protected]>
Date: Tue, 10 Apr 2018 02:48:27 +0200
Subject: [PATCH] fd_restrict prototype

---
 boost/process/detail/posix/executor.hpp        |  16 ++-
 boost/process/detail/posix/fd.hpp              |   8 ++
 boost/process/detail/posix/fd_restrict.hpp     | 154 +++++++++++++++++++++++++
 boost/process/detail/posix/file_descriptor.hpp |   7 ++
 4 files changed, 181 insertions(+), 4 deletions(-)
 create mode 100644 boost/process/detail/posix/fd_restrict.hpp

diff --git a/boost/process/detail/posix/executor.hpp b/boost/process/detail/posix/executor.hpp
index b3781f2..0a7c446 100644
--- a/boost/process/detail/posix/executor.hpp
+++ b/boost/process/detail/posix/executor.hpp
@@ -15,6 +15,7 @@
 #include <boost/process/pipe.hpp>
 #include <boost/process/detail/posix/basic_pipe.hpp>
 #include <boost/process/detail/posix/use_vfork.hpp>
+#include <boost/process/detail/posix/file_descriptor.hpp>
 #include <boost/fusion/algorithm/iteration/for_each.hpp>
 #include <cstdlib>
 #include <sys/types.h>
@@ -45,7 +46,7 @@ inline int execvpe(const char* filename, char * const arg_list[], char* env[])
 
         if (e != nullptr)
         {
-            std::vector<std::string> path; 
+            std::vector<std::string> path;
             boost::split(path, *e, boost::is_any_of(":"));
 
             for (const std::string & pp : path)
@@ -157,13 +158,13 @@ struct on_fork_success_t
 };
 
 template<typename Executor> on_setup_t  <Executor> call_on_setup  (Executor & exec) {return exec;}
-template<typename Executor> on_error_t  <Executor> call_on_error  (Executor & exec, const std::error_code & ec) 
+template<typename Executor> on_error_t  <Executor> call_on_error  (Executor & exec, const std::error_code & ec)
 {
     return on_error_t<Executor> (exec, ec);
 }
 template<typename Executor> on_success_t<Executor> call_on_success(Executor & exec) {return exec;}
 
-template<typename Executor> on_fork_error_t  <Executor> call_on_fork_error  (Executor & exec, const std::error_code & ec) 
+template<typename Executor> on_fork_error_t  <Executor> call_on_fork_error  (Executor & exec, const std::error_code & ec)
 {
     return on_fork_error_t<Executor> (exec, ec);
 }
@@ -330,6 +331,12 @@ public:
     }
     void set_error(const std::error_code &ec, const std::string &msg) {set_error(ec, msg.c_str());};
 
+    // customization point for fd_restrict
+    template <typename OutputIterator>
+    friend void collect_filedescriptors(executor const& me, OutputIterator& outit) {
+        // always protect the write end of the parent/child pipe
+        *outit++ = me._pipe_sink;
+    }
 };
 
 template<typename Sequence>
@@ -380,6 +387,7 @@ child executor<Sequence>::invoke(boost::mpl::false_, boost::mpl::false_)
         return child();
     }
     _ec.clear();
+    _pipe_sink = p[1];
     boost::fusion::for_each(seq, call_on_setup(*this));
 
     if (_ec)
@@ -391,6 +399,7 @@ child executor<Sequence>::invoke(boost::mpl::false_, boost::mpl::false_)
     this->pid = ::fork();
     if (pid == -1)
     {
+        _pipe_sink = -1;
         _ec = boost::process::detail::get_last_error();
         _msg = "fork() failed";
         boost::fusion::for_each(seq, call_on_fork_error(*this, _ec));
@@ -400,7 +409,6 @@ child executor<Sequence>::invoke(boost::mpl::false_, boost::mpl::false_)
     }
     else if (pid == 0)
     {
-        _pipe_sink = p[1];
         ::close(p[0]);
 
         boost::fusion::for_each(seq, call_on_exec_setup(*this));
diff --git a/boost/process/detail/posix/fd.hpp b/boost/process/detail/posix/fd.hpp
index 51790c3..f759d9e 100644
--- a/boost/process/detail/posix/fd.hpp
+++ b/boost/process/detail/posix/fd.hpp
@@ -11,6 +11,7 @@
 #define BOOST_PROCESS_DETAIL_POSIX_FD_HPP
 
 #include <boost/process/detail/posix/handler.hpp>
+#include <boost/process/detail/posix/fd_restrict.hpp>
 #include <unistd.h>
 
 namespace boost { namespace process { namespace detail { namespace posix {
@@ -68,6 +69,12 @@ public:
     }
 
 private:
+    // customization point for fd_restrict
+    template <typename OutputIterator>
+    friend void collect_filedescriptors(bind_fd_ const& bind_fd, OutputIterator& outit) {
+        *outit++ = bind_fd.id_;
+    }
+
     int id_;
     FileDescriptor fd_;
 };
@@ -84,6 +91,7 @@ struct fd_
     template <class FileDescriptor>
     bind_fd_<FileDescriptor> bind(int id, const FileDescriptor & fd) const {return {id, fd};}
 
+    fd_restrict::property_<> restrict_inherit(size_t capacity = 128) const {return {capacity};}
 };
 
 
diff --git a/boost/process/detail/posix/fd_restrict.hpp b/boost/process/detail/posix/fd_restrict.hpp
new file mode 100644
index 0000000..71c6c7d
--- /dev/null
+++ b/boost/process/detail/posix/fd_restrict.hpp
@@ -0,0 +1,154 @@
+// Copyright (c) 2017 Seth Heeren
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#ifndef BOOST_PROCESS_DETAIL_POSIX_FD_RESTRICT_HPP
+#define BOOST_PROCESS_DETAIL_POSIX_FD_RESTRICT_HPP
+
+#include <boost/process/detail/posix/handler.hpp>
+#include <unistd.h>
+
+namespace boost { namespace process { namespace detail { namespace posix { namespace fd_restrict {
+    // customization point for (custom) properties that need to protect fds
+    template <typename Property, typename OutputIterator>
+        void collect_filedescriptors(Property const& /*property*/, OutputIterator& /*outit*/) {
+            // primary template
+        }
+
+    // polymorphic function object for ADL dispatch
+    template <typename OutputIterator>
+    struct collect_fd_f {
+        OutputIterator mutable _outit;
+
+        template <typename Property>
+            void operator()(Property const& property) const {
+                using boost::process::detail::posix::fd_restrict::collect_filedescriptors; // ADL desired
+                collect_filedescriptors(property, _outit);
+            }
+    };
+
+    // launch property
+    template <typename=void>
+    struct property_ : handler_base_ext
+    {
+    public:
+        property_(size_t capacity) {
+            // reserve to avoid allocations between fork/exec which may
+            // deadlock with threads
+            _protected_fds.reserve(capacity);
+        }
+
+        template <class PosixExecutor>
+        void on_exec_setup(PosixExecutor& exec) const
+        {
+            _protected_fds.resize(0);
+            auto outit = back_inserter(_protected_fds);
+            collect_fd_f<decltype(outit)> visit{outit};
+
+            visit(exec);
+            boost::fusion::for_each(exec.seq, visit);
+
+            auto begin = _protected_fds.begin(), end = _protected_fds.end();
+            std::sort(begin, end);
+
+            for(int fd=0, maxfd=sysconf(_SC_OPEN_MAX); fd<maxfd; ++fd) {
+                if (!std::binary_search(begin, end, fd))
+                    ::close(fd);
+            }
+        }
+
+    private:
+        std::vector<int> mutable _protected_fds;
+    };
+
+}}}}}
+
+/* 
+ * Non-intrusive instrumentation of existing POSIX properties that require filedescriptors
+ *
+ * All of these could be done with an inline `friend` definition, like above.
+ *
+ * For now I prefer to keep them separate so that 
+ *
+ *  - upstream changes merge cleanly
+ *  - interface changes in fd_restrict can more easily be applied consistently in 1 file
+ *
+ * Only bind_fd_ and filedescriptor need friend access, so cannot usefully be kept separate.
+ */
+
+#include <boost/process/detail/posix/async_in.hpp>
+#include <boost/process/detail/posix/async_out.hpp>
+#include <boost/process/detail/posix/null_in.hpp>
+#include <boost/process/detail/posix/null_out.hpp>
+#include <boost/process/detail/posix/file_in.hpp>
+#include <boost/process/detail/posix/file_out.hpp>
+#include <boost/process/detail/posix/pipe_in.hpp>
+#include <boost/process/detail/posix/pipe_out.hpp>
+
+namespace boost { namespace process { namespace detail { namespace posix {
+
+template<typename... Ts, typename OutputIterator>
+void collect_filedescriptors(async_in_buffer<Ts...> const&, OutputIterator& outit) {
+    *outit++ = STDIN_FILENO;
+}
+
+template<int p1, int p2, typename... Ts, typename OutputIterator>
+void collect_filedescriptors(async_out_buffer<p1, p2, Ts...> const&, OutputIterator& outit) {
+    if (p1==1||p2==1) *outit++ = STDOUT_FILENO;
+    if (p1==2||p2==2) *outit++ = STDERR_FILENO;
+}
+
+template<int p1, int p2, typename... Ts, typename OutputIterator>
+void collect_filedescriptors(async_out_future<p1, p2, Ts...> const&, OutputIterator& outit) {
+    if (p1==1||p2==1) *outit++ = STDOUT_FILENO;
+    if (p1==2||p2==2) *outit++ = STDERR_FILENO;
+}
+
+template<typename OutputIterator>
+void collect_filedescriptors(file_in const&, OutputIterator& outit) {
+    *outit++ = STDIN_FILENO;
+}
+
+template<int p1, int p2, typename OutputIterator>
+void collect_filedescriptors(file_out<p1, p2> const&, OutputIterator& outit) {
+    if (p1==1||p2==1) *outit++ = STDOUT_FILENO;
+    if (p1==2||p2==2) *outit++ = STDERR_FILENO;
+}
+
+template<typename OutputIterator>
+void collect_filedescriptors(null_in const&, OutputIterator& outit) {
+    *outit++ = STDIN_FILENO;
+}
+
+template<int p1, int p2, typename OutputIterator>
+void collect_filedescriptors(null_out<p1, p2> const&, OutputIterator& outit) {
+    if (p1==1||p2==1) *outit++ = STDOUT_FILENO;
+    if (p1==2||p2==2) *outit++ = STDERR_FILENO;
+}
+
+template<typename OutputIterator>
+void collect_filedescriptors(pipe_in const&, OutputIterator& outit) {
+    *outit++ = STDIN_FILENO;
+}
+
+template<typename OutputIterator>
+void collect_filedescriptors(async_pipe_in const&, OutputIterator& outit) {
+    *outit++ = STDIN_FILENO;
+}
+
+template<int p1, int p2, typename OutputIterator>
+void collect_filedescriptors(pipe_out<p1, p2> const&, OutputIterator& outit) {
+    if (p1==1||p2==1) *outit++ = STDOUT_FILENO;
+    if (p1==2||p2==2) *outit++ = STDERR_FILENO;
+}
+
+template<int p1, int p2, typename OutputIterator>
+void collect_filedescriptors(async_pipe_out<p1, p2> const&, OutputIterator& outit) {
+    if (p1==1||p2==1) *outit++ = STDOUT_FILENO;
+    if (p1==2||p2==2) *outit++ = STDERR_FILENO;
+}
+
+}}}}
+
+#endif
diff --git a/boost/process/detail/posix/file_descriptor.hpp b/boost/process/detail/posix/file_descriptor.hpp
index 0dcb99c..0cfcfd1 100644
--- a/boost/process/detail/posix/file_descriptor.hpp
+++ b/boost/process/detail/posix/file_descriptor.hpp
@@ -53,6 +53,13 @@ struct file_descriptor
     int handle() const { return _handle;}
 
 private:
+    // customization point for fd_restrict
+    template <typename OutputIterator>
+    friend void collect_filedescriptors(file_descriptor const& property_, OutputIterator& outit) {
+        if (-1 != property_._handle)
+            *outit++ = property_._handle;
+    }
+
     static int create_file(const char* name, mode_t mode )
     {
         switch(mode)
-- 
2.16.2

I'll let the comments in the source code be your guide to understand the idea.