Skip to content

Commit

Permalink
Added limit_fd functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
klemens-morgenstern committed Jun 4, 2022
1 parent fb48747 commit 062ac9b
Show file tree
Hide file tree
Showing 11 changed files with 275 additions and 5 deletions.
14 changes: 12 additions & 2 deletions doc/v2/introduction.qbk
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ The major changes are
* Removed unreliable functionality
* UTF8 Support
* separate compilation
* fd safe by default

[section:simplified Simplified Interface]

Expand Down Expand Up @@ -56,8 +57,7 @@ file-handles for the subprocess.

Certain parts of boost.process were not as reliable as they should've been.

This is true of `limit_handles` which cannot be done safely on posix (it only finds a subset of FDs),
and especially for the `wait_for`` and `wait_until` functions on the process.
This concerns especially the `wait_for`` and `wait_until` functions on the process.
The latter are easy to do on windows, but posix does not provide an API for this.
Thus the wait_for used signals or fork, which was all but safe.
Since process v2 is based on asio and thus supports cancellation,
Expand All @@ -81,4 +81,14 @@ It can be achieved by defining `BOOST_PROCESS_V2_SEPARATE_COMPILATION` and inclu

[endsect]

[section:limit_fd Fd safe by default]

While not a problem on windows (since HANDLEs get manually enabled for inheritance),
posix systems create a problem with inheriting file handles by default.

Process V2 will automatically close all non-whitelisted descriptors,
without needing any option to enable it.

[endsect]

[endsect]
2 changes: 2 additions & 0 deletions doc/v2/launcher.qbk
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ The call sequence when exec fails:
<imagedata fileref="boost_process/posix_exec_err.svg"/>
'''

The launcher will close all non-whitelisted file descriptors after `on_exec_setup`.

[endsect]

[section:windows Windows Launchers]
Expand Down
2 changes: 2 additions & 0 deletions include/boost/process/v2/impl/default_launcher.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

#if defined(BOOST_PROCESS_V2_WINDOWS)
#include <boost/process/v2/windows/impl/default_launcher.ipp>
#else
#include <boost/process/v2/posix/detail/close_handles.ipp>
#endif


Expand Down
5 changes: 3 additions & 2 deletions include/boost/process/v2/posix/bind_fd.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,9 @@ struct bind_fd
{
if (::dup2(fd, target) == -1)
return error_code(errno, system_category());
else
return error_code ();

launcher.fd_whitelist.push_back(target);
return error_code ();
}
};

Expand Down
17 changes: 16 additions & 1 deletion include/boost/process/v2/posix/default_launcher.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include <boost/process/v2/detail/config.hpp>
#include <boost/process/v2/cstring_ref.hpp>
#include <boost/process/v2/posix/detail/close_handles.hpp>
#include <boost/process/v2/detail/utf8.hpp>

#if defined(BOOST_PROCESS_V2_STANDALONE)
Expand Down Expand Up @@ -294,6 +295,9 @@ struct default_launcher
/// The pid of the subprocess - will be assigned after fork.
int pid = -1;

/// The whitelist for file descriptors.
std::vector<int> fd_whitelist;

default_launcher() = default;

template<typename ExecutionContext, typename Args, typename ... Inits>
Expand Down Expand Up @@ -390,8 +394,12 @@ struct default_launcher
{
ctx.notify_fork(BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_child);
::close(pg.p[0]);

ec = detail::on_exec_setup(*this, executable, argv, inits...);
if (!ec)
{
fd_whitelist.push_back(pg.p[1]);
close_all_fds(ec);
}
if (!ec)
::execve(executable.c_str(), const_cast<char * const *>(argv), const_cast<char * const *>(env));

Expand Down Expand Up @@ -431,6 +439,13 @@ struct default_launcher
}
protected:

void close_all_fds(error_code & ec)
{
std::sort(fd_whitelist.begin(), fd_whitelist.end());
detail::close_all(fd_whitelist, ec);
fd_whitelist.clear();
}

struct pipe_guard
{
int p[2];
Expand Down
29 changes: 29 additions & 0 deletions include/boost/process/v2/posix/detail/close_handles.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) 2022 Klemens D. Morgenstern
//
// 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_V2_POSIX_DETAIL_CLOSE_HANDLES_HPP
#define BOOST_PROCESS_V2_POSIX_DETAIL_CLOSE_HANDLES_HPP

#include <boost/process/v2/detail/config.hpp>
#include <vector>

BOOST_PROCESS_V2_BEGIN_NAMESPACE

namespace posix
{

namespace detail
{

// whitelist must be ordered
BOOST_PROCESS_V2_DECL void close_all(const std::vector<int> & whitelist,
error_code & ec);

}

}

BOOST_PROCESS_V2_END_NAMESPACE

#endif //BOOST_PROCESS_V2_POSIX_DETAIL_CLOSE_HANDLES_HPP
197 changes: 197 additions & 0 deletions include/boost/process/v2/posix/detail/close_handles.ipp
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
//
// boost/process/v2/poxix/detail/close_handles.hpp
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2022 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net)
//
// 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_V2_POSIX_DETAIL_CLOSE_HANDLES_IPP
#define BOOST_PROCESS_V2_POSIX_DETAIL_CLOSE_HANDLES_IPP

#include <boost/process/v2/detail/config.hpp>
#include <boost/process/v2/detail/last_error.hpp>
#include <boost/process/v2/posix/detail/close_handles.hpp>
// linux has close_range since 5.19


#if defined(__NetBSD__)
|| defined(__FreeBSD__)
|| defined(__APPLE__)
|| defined(__MACH__)

// https://www.freebsd.org/cgi/man.cgi?query=close_range&apropos=0&sektion=0&manpath=FreeBSD+13.1-RELEASE+and+Ports&arch=default&format=html
// https://man.netbsd.org/closefrom.3
// __FreeBSD__
//
// gives us
//
// int closefrom(int fd);
// int close_range(u_int lowfd, u_int highfd, int flags);

#include <unistd.h>
#define BOOST_PROCESS_V2_HAS_CLOSE_RANGE_AND_CLOSEFROM 1

#elif defined(__sun)

/*https://docs.oracle.com/cd/E36784_01/html/E36874/closefrom-3c.html
int fdwalk(int (*func)(void *, int), void *cd);
*/

#include <stdlib.h>
#define BOOST_PROCESS_V2_HAS_PDFORK 1

#elif defined(__linux__)

#include <linux/version.h>

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,11,0)

// https://man7.org/linux/man-pages/man2/close_range.2.html
#include <linux/close_range.h>
#define BOOST_PROCESS_V2_HAS_CLOSE_RANGE 1
#else

#include <dirent.h>

#endif

#else

#include <dirent.h>

#endif

BOOST_PROCESS_V2_BEGIN_NAMESPACE

namespace posix
{

namespace detail
{

#if defined(BOOST_PROCESS_V2_HAS_PDFORK)

void close_all(const std::vector<int> & whitelist, error_code & ec)
{
fdwalk(+[](void * p, int fd)
{
const auto & wl = *static_cast<const std::vector<int>*>(p);
if (std::find(wl.begin(), wl.end(), fd) == wl.end())
return ::close(fd);
else
return 0;
}, const_cast<void*>(static_cast<const void*>(&whitelist)) );
ec = BOOST_PROCESS_V2_NAMESPACE::detail::get_last_error();
}

#elif defined(BOOST_PROCESS_V2_HAS_CLOSE_RANGE_AND_CLOSEFROM)

// freeBSD impl - whitelist must be ordered
void close_all(const std::vector<int> & whitelist, error_code & ec)
{
//the most common scenario is whitelist = {0,1,2}
if (!whitelist.empty())
{
if (whitelist.front() != 0)
::close_range(0, whitelist.front() - 1, CLOSE_RANGE_UNSHARE);

for (std::size_t idx = 0u;
idx < (whitelist.size() - 1u);
idx++)
{
const auto mine = whitelist[idx];
const auto next = whitelist[idx];
if ((mine + 1) != next && (mine != next))
{
::close_range(mine + 1, next - 1, CLOSE_RANGE_UNSHARE);
}
}

::closefrom(whitelist.back() + 1);
}
else
::closefrom(0);
}

#elif defined(BOOST_PROCESS_V2_HAS_CLOSE_RANGE)


// linux impl - whitelist must be ordered
void close_all(const std::vector<int> & whitelist, error_code & ec)
{
// https://patchwork.kernel.org/project/linux-fsdevel/cover/[email protected]/
//the most common scenario is whitelist = {0,1,2}
if (!whitelist.empty())
{
if (whitelist.front() != 0)
::close_range(0, whitelist.front() - 1, CLOSE_RANGE_UNSHARE);

for (std::size_t idx = 0u;
idx < (whitelist.size() - 1u);
idx++)
{
const auto mine = whitelist[idx];
const auto next = whitelist[idx];
if ((mine + 1) != next && (mine != next))
{
::close_range(mine + 1, next - 1, CLOSE_RANGE_UNSHARE);
}
}

::close_range(whitelist.back() + 1, std::numeric_limits<int>::max(), CLOSE_RANGE_UNSHARE);
}
else
::close_range(0, std::numeric_limits<int>::max(), CLOSE_RANGE_UNSHARE);
}

#else

// default one
void close_all(const std::vector<int> & whitelist, error_code & ec)
{
std::unique_ptr<DIR, void(*)(DIR*)> dir{::opendir("/dev/fd"), +[](DIR* p){::closedir(p);}};
if (dir.get() == nullptr)
{
ec = BOOST_PROCESS_V2_NAMESPACE::detail::get_last_error();
return ;
}

auto dir_fd = ::dirfd(dir.get());
if (dir_fd == -1)
{
ec = BOOST_PROCESS_V2_NAMESPACE::detail::get_last_error();
return ;
}

struct ::dirent * ent_p;

while ((ent_p = ::readdir(dir.get())) != nullptr)
{
if (ent_p->d_name[0] == '.')
continue;

const auto conv = std::atoi(ent_p->d_name);
if (conv == 0 && (ent_p->d_name[0] != '0' && ent_p->d_name[1] != '\0'))
continue;

if (conv == dir_fd
|| (std::find(whitelist.begin(), whitelist.end(), conv) != whitelist.end()))
continue;

::close(conv);
}
}

#endif

}

}

BOOST_PROCESS_V2_END_NAMESPACE

#endif //BOOST_PROCESS_V2_POSIX_DETAIL_CLOSE_HANDLES_IPP
2 changes: 2 additions & 0 deletions include/boost/process/v2/posix/fork_and_forget_launcher.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ struct fork_and_forget_launcher : default_launcher
ctx.notify_fork(BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_child);

ec = detail::on_exec_setup(*this, executable, argv, inits...);
if (!ec)
close_all_fds(ec);
if (!ec)
::execve(executable.c_str(), const_cast<char * const *>(argv), const_cast<char * const *>(env));

Expand Down
5 changes: 5 additions & 0 deletions include/boost/process/v2/posix/pdfork_launcher.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ struct pdfork_launcher : default_launcher
::close(pg.p[0]);

ec = detail::on_exec_setup(*this, executable, argv, inits...);
if (!ec)
{
fd_whitelist.push_back(pg.p[1]);
close_all_fds(ec);
}
if (!ec)
::execve(executable.c_str(), const_cast<char * const *>(argv), const_cast<char * const *>(env));

Expand Down
2 changes: 2 additions & 0 deletions include/boost/process/v2/posix/vfork_launcher.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ struct vfork_launcher : default_launcher
{
ctx.notify_fork(BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_child);
ec = detail::on_exec_setup(*this, executable, argv, inits...);
if (!ec)
close_all_fds(ec);
if (!ec)
::execve(executable.c_str(), const_cast<char * const *>(argv), const_cast<char * const *>(env));

Expand Down
5 changes: 5 additions & 0 deletions include/boost/process/v2/stdio.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,11 @@ struct process_stdio
if (::dup2(err.fd, err.target) == -1)
return error_code(errno, system_category());


launcher.fd_whitelist.push_back(STDIN_FILENO);
launcher.fd_whitelist.push_back(STDOUT_FILENO);
launcher.fd_whitelist.push_back(STDERR_FILENO);

return error_code {};
};
#endif
Expand Down

0 comments on commit 062ac9b

Please sign in to comment.