1*b0d29bc4SBrooks Davis // Copyright 2014 The Kyua Authors.
2*b0d29bc4SBrooks Davis // All rights reserved.
3*b0d29bc4SBrooks Davis //
4*b0d29bc4SBrooks Davis // Redistribution and use in source and binary forms, with or without
5*b0d29bc4SBrooks Davis // modification, are permitted provided that the following conditions are
6*b0d29bc4SBrooks Davis // met:
7*b0d29bc4SBrooks Davis //
8*b0d29bc4SBrooks Davis // * Redistributions of source code must retain the above copyright
9*b0d29bc4SBrooks Davis // notice, this list of conditions and the following disclaimer.
10*b0d29bc4SBrooks Davis // * Redistributions in binary form must reproduce the above copyright
11*b0d29bc4SBrooks Davis // notice, this list of conditions and the following disclaimer in the
12*b0d29bc4SBrooks Davis // documentation and/or other materials provided with the distribution.
13*b0d29bc4SBrooks Davis // * Neither the name of Google Inc. nor the names of its contributors
14*b0d29bc4SBrooks Davis // may be used to endorse or promote products derived from this software
15*b0d29bc4SBrooks Davis // without specific prior written permission.
16*b0d29bc4SBrooks Davis //
17*b0d29bc4SBrooks Davis // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18*b0d29bc4SBrooks Davis // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19*b0d29bc4SBrooks Davis // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20*b0d29bc4SBrooks Davis // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21*b0d29bc4SBrooks Davis // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22*b0d29bc4SBrooks Davis // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23*b0d29bc4SBrooks Davis // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24*b0d29bc4SBrooks Davis // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25*b0d29bc4SBrooks Davis // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26*b0d29bc4SBrooks Davis // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27*b0d29bc4SBrooks Davis // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28*b0d29bc4SBrooks Davis
29*b0d29bc4SBrooks Davis #include "utils/process/isolation.hpp"
30*b0d29bc4SBrooks Davis
31*b0d29bc4SBrooks Davis extern "C" {
32*b0d29bc4SBrooks Davis #include <sys/types.h>
33*b0d29bc4SBrooks Davis #include <sys/resource.h>
34*b0d29bc4SBrooks Davis #include <sys/stat.h>
35*b0d29bc4SBrooks Davis
36*b0d29bc4SBrooks Davis #include <unistd.h>
37*b0d29bc4SBrooks Davis }
38*b0d29bc4SBrooks Davis
39*b0d29bc4SBrooks Davis #include <cerrno>
40*b0d29bc4SBrooks Davis #include <cstdlib>
41*b0d29bc4SBrooks Davis #include <fstream>
42*b0d29bc4SBrooks Davis #include <iostream>
43*b0d29bc4SBrooks Davis
44*b0d29bc4SBrooks Davis #include <atf-c++.hpp>
45*b0d29bc4SBrooks Davis
46*b0d29bc4SBrooks Davis #include "utils/defs.hpp"
47*b0d29bc4SBrooks Davis #include "utils/env.hpp"
48*b0d29bc4SBrooks Davis #include "utils/format/macros.hpp"
49*b0d29bc4SBrooks Davis #include "utils/fs/operations.hpp"
50*b0d29bc4SBrooks Davis #include "utils/fs/path.hpp"
51*b0d29bc4SBrooks Davis #include "utils/optional.ipp"
52*b0d29bc4SBrooks Davis #include "utils/passwd.hpp"
53*b0d29bc4SBrooks Davis #include "utils/process/child.ipp"
54*b0d29bc4SBrooks Davis #include "utils/process/status.hpp"
55*b0d29bc4SBrooks Davis #include "utils/sanity.hpp"
56*b0d29bc4SBrooks Davis #include "utils/test_utils.ipp"
57*b0d29bc4SBrooks Davis
58*b0d29bc4SBrooks Davis namespace fs = utils::fs;
59*b0d29bc4SBrooks Davis namespace passwd = utils::passwd;
60*b0d29bc4SBrooks Davis namespace process = utils::process;
61*b0d29bc4SBrooks Davis
62*b0d29bc4SBrooks Davis using utils::none;
63*b0d29bc4SBrooks Davis using utils::optional;
64*b0d29bc4SBrooks Davis
65*b0d29bc4SBrooks Davis
66*b0d29bc4SBrooks Davis namespace {
67*b0d29bc4SBrooks Davis
68*b0d29bc4SBrooks Davis
69*b0d29bc4SBrooks Davis /// Runs the given hook in a subprocess.
70*b0d29bc4SBrooks Davis ///
71*b0d29bc4SBrooks Davis /// \param hook The code to run in the subprocess.
72*b0d29bc4SBrooks Davis ///
73*b0d29bc4SBrooks Davis /// \return The status of the subprocess for further validation.
74*b0d29bc4SBrooks Davis ///
75*b0d29bc4SBrooks Davis /// \post The subprocess.stdout and subprocess.stderr files, created in the
76*b0d29bc4SBrooks Davis /// current directory, contain the output of the subprocess.
77*b0d29bc4SBrooks Davis template< typename Hook >
78*b0d29bc4SBrooks Davis static process::status
fork_and_run(Hook hook)79*b0d29bc4SBrooks Davis fork_and_run(Hook hook)
80*b0d29bc4SBrooks Davis {
81*b0d29bc4SBrooks Davis std::auto_ptr< process::child > child = process::child::fork_files(
82*b0d29bc4SBrooks Davis hook, fs::path("subprocess.stdout"), fs::path("subprocess.stderr"));
83*b0d29bc4SBrooks Davis const process::status status = child->wait();
84*b0d29bc4SBrooks Davis
85*b0d29bc4SBrooks Davis atf::utils::cat_file("subprocess.stdout", "isolated child stdout: ");
86*b0d29bc4SBrooks Davis atf::utils::cat_file("subprocess.stderr", "isolated child stderr: ");
87*b0d29bc4SBrooks Davis
88*b0d29bc4SBrooks Davis return status;
89*b0d29bc4SBrooks Davis }
90*b0d29bc4SBrooks Davis
91*b0d29bc4SBrooks Davis
92*b0d29bc4SBrooks Davis /// Subprocess that validates the cleanliness of the environment.
93*b0d29bc4SBrooks Davis ///
94*b0d29bc4SBrooks Davis /// \post Exits with success if the environment is clean; failure otherwise.
95*b0d29bc4SBrooks Davis static void
check_clean_environment(void)96*b0d29bc4SBrooks Davis check_clean_environment(void)
97*b0d29bc4SBrooks Davis {
98*b0d29bc4SBrooks Davis fs::mkdir(fs::path("some-directory"), 0755);
99*b0d29bc4SBrooks Davis process::isolate_child(none, fs::path("some-directory"));
100*b0d29bc4SBrooks Davis
101*b0d29bc4SBrooks Davis bool failed = false;
102*b0d29bc4SBrooks Davis
103*b0d29bc4SBrooks Davis const char* empty[] = { "LANG", "LC_ALL", "LC_COLLATE", "LC_CTYPE",
104*b0d29bc4SBrooks Davis "LC_MESSAGES", "LC_MONETARY", "LC_NUMERIC",
105*b0d29bc4SBrooks Davis "LC_TIME", NULL };
106*b0d29bc4SBrooks Davis const char** iter;
107*b0d29bc4SBrooks Davis for (iter = empty; *iter != NULL; ++iter) {
108*b0d29bc4SBrooks Davis if (utils::getenv(*iter)) {
109*b0d29bc4SBrooks Davis failed = true;
110*b0d29bc4SBrooks Davis std::cout << F("%s was not unset\n") % *iter;
111*b0d29bc4SBrooks Davis }
112*b0d29bc4SBrooks Davis }
113*b0d29bc4SBrooks Davis
114*b0d29bc4SBrooks Davis if (utils::getenv_with_default("HOME", "") != "some-directory") {
115*b0d29bc4SBrooks Davis failed = true;
116*b0d29bc4SBrooks Davis std::cout << "HOME was not set to the work directory\n";
117*b0d29bc4SBrooks Davis }
118*b0d29bc4SBrooks Davis
119*b0d29bc4SBrooks Davis if (utils::getenv_with_default("TMPDIR", "") != "some-directory") {
120*b0d29bc4SBrooks Davis failed = true;
121*b0d29bc4SBrooks Davis std::cout << "TMPDIR was not set to the work directory\n";
122*b0d29bc4SBrooks Davis }
123*b0d29bc4SBrooks Davis
124*b0d29bc4SBrooks Davis if (utils::getenv_with_default("TZ", "") != "UTC") {
125*b0d29bc4SBrooks Davis failed = true;
126*b0d29bc4SBrooks Davis std::cout << "TZ was not set to UTC\n";
127*b0d29bc4SBrooks Davis }
128*b0d29bc4SBrooks Davis
129*b0d29bc4SBrooks Davis if (utils::getenv_with_default("LEAVE_ME_ALONE", "") != "kill-some-day") {
130*b0d29bc4SBrooks Davis failed = true;
131*b0d29bc4SBrooks Davis std::cout << "LEAVE_ME_ALONE was modified while it should not have "
132*b0d29bc4SBrooks Davis "been\n";
133*b0d29bc4SBrooks Davis }
134*b0d29bc4SBrooks Davis
135*b0d29bc4SBrooks Davis std::exit(failed ? EXIT_FAILURE : EXIT_SUCCESS);
136*b0d29bc4SBrooks Davis }
137*b0d29bc4SBrooks Davis
138*b0d29bc4SBrooks Davis
139*b0d29bc4SBrooks Davis /// Subprocess that checks if user privileges are dropped.
140*b0d29bc4SBrooks Davis class check_drop_privileges {
141*b0d29bc4SBrooks Davis /// The user to drop the privileges to.
142*b0d29bc4SBrooks Davis const passwd::user _unprivileged_user;
143*b0d29bc4SBrooks Davis
144*b0d29bc4SBrooks Davis public:
145*b0d29bc4SBrooks Davis /// Constructor.
146*b0d29bc4SBrooks Davis ///
147*b0d29bc4SBrooks Davis /// \param unprivileged_user The user to drop the privileges to.
check_drop_privileges(const passwd::user & unprivileged_user)148*b0d29bc4SBrooks Davis check_drop_privileges(const passwd::user& unprivileged_user) :
149*b0d29bc4SBrooks Davis _unprivileged_user(unprivileged_user)
150*b0d29bc4SBrooks Davis {
151*b0d29bc4SBrooks Davis }
152*b0d29bc4SBrooks Davis
153*b0d29bc4SBrooks Davis /// Body of the subprocess.
154*b0d29bc4SBrooks Davis ///
155*b0d29bc4SBrooks Davis /// \post Exits with success if the process has dropped privileges as
156*b0d29bc4SBrooks Davis /// expected.
157*b0d29bc4SBrooks Davis void
operator ()(void) const158*b0d29bc4SBrooks Davis operator()(void) const
159*b0d29bc4SBrooks Davis {
160*b0d29bc4SBrooks Davis fs::mkdir(fs::path("subdir"), 0755);
161*b0d29bc4SBrooks Davis process::isolate_child(utils::make_optional(_unprivileged_user),
162*b0d29bc4SBrooks Davis fs::path("subdir"));
163*b0d29bc4SBrooks Davis
164*b0d29bc4SBrooks Davis if (::getuid() == 0) {
165*b0d29bc4SBrooks Davis std::cout << "UID is still 0\n";
166*b0d29bc4SBrooks Davis std::exit(EXIT_FAILURE);
167*b0d29bc4SBrooks Davis }
168*b0d29bc4SBrooks Davis
169*b0d29bc4SBrooks Davis if (::getgid() == 0) {
170*b0d29bc4SBrooks Davis std::cout << "GID is still 0\n";
171*b0d29bc4SBrooks Davis std::exit(EXIT_FAILURE);
172*b0d29bc4SBrooks Davis }
173*b0d29bc4SBrooks Davis
174*b0d29bc4SBrooks Davis ::gid_t groups[1];
175*b0d29bc4SBrooks Davis if (::getgroups(1, groups) == -1) {
176*b0d29bc4SBrooks Davis // Should only fail if we get more than one group notifying about
177*b0d29bc4SBrooks Davis // not enough space in the groups variable to store the whole
178*b0d29bc4SBrooks Davis // result.
179*b0d29bc4SBrooks Davis INV(errno == EINVAL);
180*b0d29bc4SBrooks Davis std::exit(EXIT_FAILURE);
181*b0d29bc4SBrooks Davis }
182*b0d29bc4SBrooks Davis if (groups[0] == 0) {
183*b0d29bc4SBrooks Davis std::cout << "Primary group is still 0\n";
184*b0d29bc4SBrooks Davis std::exit(EXIT_FAILURE);
185*b0d29bc4SBrooks Davis }
186*b0d29bc4SBrooks Davis
187*b0d29bc4SBrooks Davis std::ofstream output("file.txt");
188*b0d29bc4SBrooks Davis if (!output) {
189*b0d29bc4SBrooks Davis std::cout << "Cannot write to isolated directory; owner not "
190*b0d29bc4SBrooks Davis "changed?\n";
191*b0d29bc4SBrooks Davis std::exit(EXIT_FAILURE);
192*b0d29bc4SBrooks Davis }
193*b0d29bc4SBrooks Davis
194*b0d29bc4SBrooks Davis std::exit(EXIT_SUCCESS);
195*b0d29bc4SBrooks Davis }
196*b0d29bc4SBrooks Davis };
197*b0d29bc4SBrooks Davis
198*b0d29bc4SBrooks Davis
199*b0d29bc4SBrooks Davis /// Subprocess that dumps core to validate core dumping abilities.
200*b0d29bc4SBrooks Davis static void
check_enable_core_dumps(void)201*b0d29bc4SBrooks Davis check_enable_core_dumps(void)
202*b0d29bc4SBrooks Davis {
203*b0d29bc4SBrooks Davis process::isolate_child(none, fs::path("."));
204*b0d29bc4SBrooks Davis std::abort();
205*b0d29bc4SBrooks Davis }
206*b0d29bc4SBrooks Davis
207*b0d29bc4SBrooks Davis
208*b0d29bc4SBrooks Davis /// Subprocess that checks if the work directory is entered.
209*b0d29bc4SBrooks Davis class check_enter_work_directory {
210*b0d29bc4SBrooks Davis /// Directory to enter. May be releative.
211*b0d29bc4SBrooks Davis const fs::path _directory;
212*b0d29bc4SBrooks Davis
213*b0d29bc4SBrooks Davis public:
214*b0d29bc4SBrooks Davis /// Constructor.
215*b0d29bc4SBrooks Davis ///
216*b0d29bc4SBrooks Davis /// \param directory Directory to enter.
check_enter_work_directory(const fs::path & directory)217*b0d29bc4SBrooks Davis check_enter_work_directory(const fs::path& directory) :
218*b0d29bc4SBrooks Davis _directory(directory)
219*b0d29bc4SBrooks Davis {
220*b0d29bc4SBrooks Davis }
221*b0d29bc4SBrooks Davis
222*b0d29bc4SBrooks Davis /// Body of the subprocess.
223*b0d29bc4SBrooks Davis ///
224*b0d29bc4SBrooks Davis /// \post Exits with success if the process has entered the given work
225*b0d29bc4SBrooks Davis /// directory; false otherwise.
226*b0d29bc4SBrooks Davis void
operator ()(void) const227*b0d29bc4SBrooks Davis operator()(void) const
228*b0d29bc4SBrooks Davis {
229*b0d29bc4SBrooks Davis const fs::path exp_subdir = fs::current_path() / _directory;
230*b0d29bc4SBrooks Davis process::isolate_child(none, _directory);
231*b0d29bc4SBrooks Davis std::exit(fs::current_path() == exp_subdir ?
232*b0d29bc4SBrooks Davis EXIT_SUCCESS : EXIT_FAILURE);
233*b0d29bc4SBrooks Davis }
234*b0d29bc4SBrooks Davis };
235*b0d29bc4SBrooks Davis
236*b0d29bc4SBrooks Davis
237*b0d29bc4SBrooks Davis /// Subprocess that validates that it owns a session.
238*b0d29bc4SBrooks Davis ///
239*b0d29bc4SBrooks Davis /// \post Exits with success if the process lives in its own session;
240*b0d29bc4SBrooks Davis /// failure otherwise.
241*b0d29bc4SBrooks Davis static void
check_new_session(void)242*b0d29bc4SBrooks Davis check_new_session(void)
243*b0d29bc4SBrooks Davis {
244*b0d29bc4SBrooks Davis process::isolate_child(none, fs::path("."));
245*b0d29bc4SBrooks Davis std::exit(::getsid(::getpid()) == ::getpid() ? EXIT_SUCCESS : EXIT_FAILURE);
246*b0d29bc4SBrooks Davis }
247*b0d29bc4SBrooks Davis
248*b0d29bc4SBrooks Davis
249*b0d29bc4SBrooks Davis /// Subprocess that validates the disconnection from any terminal.
250*b0d29bc4SBrooks Davis ///
251*b0d29bc4SBrooks Davis /// \post Exits with success if the environment is clean; failure otherwise.
252*b0d29bc4SBrooks Davis static void
check_no_terminal(void)253*b0d29bc4SBrooks Davis check_no_terminal(void)
254*b0d29bc4SBrooks Davis {
255*b0d29bc4SBrooks Davis process::isolate_child(none, fs::path("."));
256*b0d29bc4SBrooks Davis
257*b0d29bc4SBrooks Davis const char* const args[] = {
258*b0d29bc4SBrooks Davis "/bin/sh",
259*b0d29bc4SBrooks Davis "-i",
260*b0d29bc4SBrooks Davis "-c",
261*b0d29bc4SBrooks Davis "echo success",
262*b0d29bc4SBrooks Davis NULL
263*b0d29bc4SBrooks Davis };
264*b0d29bc4SBrooks Davis ::execv("/bin/sh", UTILS_UNCONST(char*, args));
265*b0d29bc4SBrooks Davis std::abort();
266*b0d29bc4SBrooks Davis }
267*b0d29bc4SBrooks Davis
268*b0d29bc4SBrooks Davis
269*b0d29bc4SBrooks Davis /// Subprocess that validates that it has become the leader of a process group.
270*b0d29bc4SBrooks Davis ///
271*b0d29bc4SBrooks Davis /// \post Exits with success if the process lives in its own process group;
272*b0d29bc4SBrooks Davis /// failure otherwise.
273*b0d29bc4SBrooks Davis static void
check_process_group(void)274*b0d29bc4SBrooks Davis check_process_group(void)
275*b0d29bc4SBrooks Davis {
276*b0d29bc4SBrooks Davis process::isolate_child(none, fs::path("."));
277*b0d29bc4SBrooks Davis std::exit(::getpgid(::getpid()) == ::getpid() ?
278*b0d29bc4SBrooks Davis EXIT_SUCCESS : EXIT_FAILURE);
279*b0d29bc4SBrooks Davis }
280*b0d29bc4SBrooks Davis
281*b0d29bc4SBrooks Davis
282*b0d29bc4SBrooks Davis /// Subprocess that validates that the umask has been reset.
283*b0d29bc4SBrooks Davis ///
284*b0d29bc4SBrooks Davis /// \post Exits with success if the umask matches the expected value; failure
285*b0d29bc4SBrooks Davis /// otherwise.
286*b0d29bc4SBrooks Davis static void
check_umask(void)287*b0d29bc4SBrooks Davis check_umask(void)
288*b0d29bc4SBrooks Davis {
289*b0d29bc4SBrooks Davis process::isolate_child(none, fs::path("."));
290*b0d29bc4SBrooks Davis std::exit(::umask(0) == 0022 ? EXIT_SUCCESS : EXIT_FAILURE);
291*b0d29bc4SBrooks Davis }
292*b0d29bc4SBrooks Davis
293*b0d29bc4SBrooks Davis
294*b0d29bc4SBrooks Davis } // anonymous namespace
295*b0d29bc4SBrooks Davis
296*b0d29bc4SBrooks Davis
297*b0d29bc4SBrooks Davis ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__clean_environment);
ATF_TEST_CASE_BODY(isolate_child__clean_environment)298*b0d29bc4SBrooks Davis ATF_TEST_CASE_BODY(isolate_child__clean_environment)
299*b0d29bc4SBrooks Davis {
300*b0d29bc4SBrooks Davis utils::setenv("HOME", "/non-existent/directory");
301*b0d29bc4SBrooks Davis utils::setenv("TMPDIR", "/non-existent/directory");
302*b0d29bc4SBrooks Davis utils::setenv("LANG", "C");
303*b0d29bc4SBrooks Davis utils::setenv("LC_ALL", "C");
304*b0d29bc4SBrooks Davis utils::setenv("LC_COLLATE", "C");
305*b0d29bc4SBrooks Davis utils::setenv("LC_CTYPE", "C");
306*b0d29bc4SBrooks Davis utils::setenv("LC_MESSAGES", "C");
307*b0d29bc4SBrooks Davis utils::setenv("LC_MONETARY", "C");
308*b0d29bc4SBrooks Davis utils::setenv("LC_NUMERIC", "C");
309*b0d29bc4SBrooks Davis utils::setenv("LC_TIME", "C");
310*b0d29bc4SBrooks Davis utils::setenv("LEAVE_ME_ALONE", "kill-some-day");
311*b0d29bc4SBrooks Davis utils::setenv("TZ", "EST+5");
312*b0d29bc4SBrooks Davis
313*b0d29bc4SBrooks Davis const process::status status = fork_and_run(check_clean_environment);
314*b0d29bc4SBrooks Davis ATF_REQUIRE(status.exited());
315*b0d29bc4SBrooks Davis ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus());
316*b0d29bc4SBrooks Davis }
317*b0d29bc4SBrooks Davis
318*b0d29bc4SBrooks Davis
319*b0d29bc4SBrooks Davis ATF_TEST_CASE(isolate_child__other_user_when_unprivileged);
ATF_TEST_CASE_HEAD(isolate_child__other_user_when_unprivileged)320*b0d29bc4SBrooks Davis ATF_TEST_CASE_HEAD(isolate_child__other_user_when_unprivileged)
321*b0d29bc4SBrooks Davis {
322*b0d29bc4SBrooks Davis set_md_var("require.user", "unprivileged");
323*b0d29bc4SBrooks Davis }
ATF_TEST_CASE_BODY(isolate_child__other_user_when_unprivileged)324*b0d29bc4SBrooks Davis ATF_TEST_CASE_BODY(isolate_child__other_user_when_unprivileged)
325*b0d29bc4SBrooks Davis {
326*b0d29bc4SBrooks Davis const passwd::user user = passwd::current_user();
327*b0d29bc4SBrooks Davis
328*b0d29bc4SBrooks Davis passwd::user other_user = user;
329*b0d29bc4SBrooks Davis other_user.uid += 1;
330*b0d29bc4SBrooks Davis other_user.gid += 1;
331*b0d29bc4SBrooks Davis process::isolate_child(utils::make_optional(other_user), fs::path("."));
332*b0d29bc4SBrooks Davis
333*b0d29bc4SBrooks Davis ATF_REQUIRE_EQ(user.uid, ::getuid());
334*b0d29bc4SBrooks Davis ATF_REQUIRE_EQ(user.gid, ::getgid());
335*b0d29bc4SBrooks Davis }
336*b0d29bc4SBrooks Davis
337*b0d29bc4SBrooks Davis
338*b0d29bc4SBrooks Davis ATF_TEST_CASE(isolate_child__drop_privileges);
ATF_TEST_CASE_HEAD(isolate_child__drop_privileges)339*b0d29bc4SBrooks Davis ATF_TEST_CASE_HEAD(isolate_child__drop_privileges)
340*b0d29bc4SBrooks Davis {
341*b0d29bc4SBrooks Davis set_md_var("require.config", "unprivileged-user");
342*b0d29bc4SBrooks Davis set_md_var("require.user", "root");
343*b0d29bc4SBrooks Davis }
ATF_TEST_CASE_BODY(isolate_child__drop_privileges)344*b0d29bc4SBrooks Davis ATF_TEST_CASE_BODY(isolate_child__drop_privileges)
345*b0d29bc4SBrooks Davis {
346*b0d29bc4SBrooks Davis const passwd::user unprivileged_user = passwd::find_user_by_name(
347*b0d29bc4SBrooks Davis get_config_var("unprivileged-user"));
348*b0d29bc4SBrooks Davis
349*b0d29bc4SBrooks Davis const process::status status = fork_and_run(check_drop_privileges(
350*b0d29bc4SBrooks Davis unprivileged_user));
351*b0d29bc4SBrooks Davis ATF_REQUIRE(status.exited());
352*b0d29bc4SBrooks Davis ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus());
353*b0d29bc4SBrooks Davis }
354*b0d29bc4SBrooks Davis
355*b0d29bc4SBrooks Davis
356*b0d29bc4SBrooks Davis ATF_TEST_CASE(isolate_child__drop_privileges_fail_uid);
ATF_TEST_CASE_HEAD(isolate_child__drop_privileges_fail_uid)357*b0d29bc4SBrooks Davis ATF_TEST_CASE_HEAD(isolate_child__drop_privileges_fail_uid)
358*b0d29bc4SBrooks Davis {
359*b0d29bc4SBrooks Davis set_md_var("require.user", "unprivileged");
360*b0d29bc4SBrooks Davis }
ATF_TEST_CASE_BODY(isolate_child__drop_privileges_fail_uid)361*b0d29bc4SBrooks Davis ATF_TEST_CASE_BODY(isolate_child__drop_privileges_fail_uid)
362*b0d29bc4SBrooks Davis {
363*b0d29bc4SBrooks Davis // Fake the current user as root so that we bypass the protections in
364*b0d29bc4SBrooks Davis // isolate_child that prevent us from attempting a user switch when we are
365*b0d29bc4SBrooks Davis // not root. We do this so we can trigger the setuid failure.
366*b0d29bc4SBrooks Davis passwd::user root = passwd::user("root", 0, 0);
367*b0d29bc4SBrooks Davis ATF_REQUIRE(root.is_root());
368*b0d29bc4SBrooks Davis passwd::set_current_user_for_testing(root);
369*b0d29bc4SBrooks Davis
370*b0d29bc4SBrooks Davis passwd::user unprivileged_user = passwd::current_user();
371*b0d29bc4SBrooks Davis unprivileged_user.uid += 1;
372*b0d29bc4SBrooks Davis
373*b0d29bc4SBrooks Davis const process::status status = fork_and_run(check_drop_privileges(
374*b0d29bc4SBrooks Davis unprivileged_user));
375*b0d29bc4SBrooks Davis ATF_REQUIRE(status.exited());
376*b0d29bc4SBrooks Davis ATF_REQUIRE_EQ(process::exit_isolation_failure, status.exitstatus());
377*b0d29bc4SBrooks Davis ATF_REQUIRE(atf::utils::grep_file("(chown|setuid).*failed",
378*b0d29bc4SBrooks Davis "subprocess.stderr"));
379*b0d29bc4SBrooks Davis }
380*b0d29bc4SBrooks Davis
381*b0d29bc4SBrooks Davis
382*b0d29bc4SBrooks Davis ATF_TEST_CASE(isolate_child__drop_privileges_fail_gid);
ATF_TEST_CASE_HEAD(isolate_child__drop_privileges_fail_gid)383*b0d29bc4SBrooks Davis ATF_TEST_CASE_HEAD(isolate_child__drop_privileges_fail_gid)
384*b0d29bc4SBrooks Davis {
385*b0d29bc4SBrooks Davis set_md_var("require.user", "unprivileged");
386*b0d29bc4SBrooks Davis }
ATF_TEST_CASE_BODY(isolate_child__drop_privileges_fail_gid)387*b0d29bc4SBrooks Davis ATF_TEST_CASE_BODY(isolate_child__drop_privileges_fail_gid)
388*b0d29bc4SBrooks Davis {
389*b0d29bc4SBrooks Davis // Fake the current user as root so that we bypass the protections in
390*b0d29bc4SBrooks Davis // isolate_child that prevent us from attempting a user switch when we are
391*b0d29bc4SBrooks Davis // not root. We do this so we can trigger the setgid failure.
392*b0d29bc4SBrooks Davis passwd::user root = passwd::user("root", 0, 0);
393*b0d29bc4SBrooks Davis ATF_REQUIRE(root.is_root());
394*b0d29bc4SBrooks Davis passwd::set_current_user_for_testing(root);
395*b0d29bc4SBrooks Davis
396*b0d29bc4SBrooks Davis passwd::user unprivileged_user = passwd::current_user();
397*b0d29bc4SBrooks Davis unprivileged_user.gid += 1;
398*b0d29bc4SBrooks Davis
399*b0d29bc4SBrooks Davis const process::status status = fork_and_run(check_drop_privileges(
400*b0d29bc4SBrooks Davis unprivileged_user));
401*b0d29bc4SBrooks Davis ATF_REQUIRE(status.exited());
402*b0d29bc4SBrooks Davis ATF_REQUIRE_EQ(process::exit_isolation_failure, status.exitstatus());
403*b0d29bc4SBrooks Davis ATF_REQUIRE(atf::utils::grep_file("(chown|setgid).*failed",
404*b0d29bc4SBrooks Davis "subprocess.stderr"));
405*b0d29bc4SBrooks Davis }
406*b0d29bc4SBrooks Davis
407*b0d29bc4SBrooks Davis
408*b0d29bc4SBrooks Davis ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__enable_core_dumps);
ATF_TEST_CASE_BODY(isolate_child__enable_core_dumps)409*b0d29bc4SBrooks Davis ATF_TEST_CASE_BODY(isolate_child__enable_core_dumps)
410*b0d29bc4SBrooks Davis {
411*b0d29bc4SBrooks Davis utils::require_run_coredump_tests(this);
412*b0d29bc4SBrooks Davis
413*b0d29bc4SBrooks Davis struct ::rlimit rl;
414*b0d29bc4SBrooks Davis if (::getrlimit(RLIMIT_CORE, &rl) == -1)
415*b0d29bc4SBrooks Davis fail("Failed to query the core size limit");
416*b0d29bc4SBrooks Davis if (rl.rlim_cur == 0 || rl.rlim_max == 0)
417*b0d29bc4SBrooks Davis skip("Maximum core size is zero; cannot run test");
418*b0d29bc4SBrooks Davis rl.rlim_cur = 0;
419*b0d29bc4SBrooks Davis if (::setrlimit(RLIMIT_CORE, &rl) == -1)
420*b0d29bc4SBrooks Davis fail("Failed to lower the core size limit");
421*b0d29bc4SBrooks Davis
422*b0d29bc4SBrooks Davis const process::status status = fork_and_run(check_enable_core_dumps);
423*b0d29bc4SBrooks Davis ATF_REQUIRE(status.signaled());
424*b0d29bc4SBrooks Davis ATF_REQUIRE(status.coredump());
425*b0d29bc4SBrooks Davis }
426*b0d29bc4SBrooks Davis
427*b0d29bc4SBrooks Davis
428*b0d29bc4SBrooks Davis ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__enter_work_directory);
ATF_TEST_CASE_BODY(isolate_child__enter_work_directory)429*b0d29bc4SBrooks Davis ATF_TEST_CASE_BODY(isolate_child__enter_work_directory)
430*b0d29bc4SBrooks Davis {
431*b0d29bc4SBrooks Davis const fs::path directory("some/sub/directory");
432*b0d29bc4SBrooks Davis fs::mkdir_p(directory, 0755);
433*b0d29bc4SBrooks Davis const process::status status = fork_and_run(
434*b0d29bc4SBrooks Davis check_enter_work_directory(directory));
435*b0d29bc4SBrooks Davis ATF_REQUIRE(status.exited());
436*b0d29bc4SBrooks Davis ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus());
437*b0d29bc4SBrooks Davis }
438*b0d29bc4SBrooks Davis
439*b0d29bc4SBrooks Davis
440*b0d29bc4SBrooks Davis ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__enter_work_directory_failure);
ATF_TEST_CASE_BODY(isolate_child__enter_work_directory_failure)441*b0d29bc4SBrooks Davis ATF_TEST_CASE_BODY(isolate_child__enter_work_directory_failure)
442*b0d29bc4SBrooks Davis {
443*b0d29bc4SBrooks Davis const fs::path directory("some/sub/directory");
444*b0d29bc4SBrooks Davis const process::status status = fork_and_run(
445*b0d29bc4SBrooks Davis check_enter_work_directory(directory));
446*b0d29bc4SBrooks Davis ATF_REQUIRE(status.exited());
447*b0d29bc4SBrooks Davis ATF_REQUIRE_EQ(process::exit_isolation_failure, status.exitstatus());
448*b0d29bc4SBrooks Davis ATF_REQUIRE(atf::utils::grep_file("chdir\\(some/sub/directory\\) failed",
449*b0d29bc4SBrooks Davis "subprocess.stderr"));
450*b0d29bc4SBrooks Davis }
451*b0d29bc4SBrooks Davis
452*b0d29bc4SBrooks Davis
453*b0d29bc4SBrooks Davis ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__new_session);
ATF_TEST_CASE_BODY(isolate_child__new_session)454*b0d29bc4SBrooks Davis ATF_TEST_CASE_BODY(isolate_child__new_session)
455*b0d29bc4SBrooks Davis {
456*b0d29bc4SBrooks Davis const process::status status = fork_and_run(check_new_session);
457*b0d29bc4SBrooks Davis ATF_REQUIRE(status.exited());
458*b0d29bc4SBrooks Davis ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus());
459*b0d29bc4SBrooks Davis }
460*b0d29bc4SBrooks Davis
461*b0d29bc4SBrooks Davis
462*b0d29bc4SBrooks Davis ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__no_terminal);
ATF_TEST_CASE_BODY(isolate_child__no_terminal)463*b0d29bc4SBrooks Davis ATF_TEST_CASE_BODY(isolate_child__no_terminal)
464*b0d29bc4SBrooks Davis {
465*b0d29bc4SBrooks Davis const process::status status = fork_and_run(check_no_terminal);
466*b0d29bc4SBrooks Davis ATF_REQUIRE(status.exited());
467*b0d29bc4SBrooks Davis ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus());
468*b0d29bc4SBrooks Davis }
469*b0d29bc4SBrooks Davis
470*b0d29bc4SBrooks Davis
471*b0d29bc4SBrooks Davis ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__process_group);
ATF_TEST_CASE_BODY(isolate_child__process_group)472*b0d29bc4SBrooks Davis ATF_TEST_CASE_BODY(isolate_child__process_group)
473*b0d29bc4SBrooks Davis {
474*b0d29bc4SBrooks Davis const process::status status = fork_and_run(check_process_group);
475*b0d29bc4SBrooks Davis ATF_REQUIRE(status.exited());
476*b0d29bc4SBrooks Davis ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus());
477*b0d29bc4SBrooks Davis }
478*b0d29bc4SBrooks Davis
479*b0d29bc4SBrooks Davis
480*b0d29bc4SBrooks Davis ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__reset_umask);
ATF_TEST_CASE_BODY(isolate_child__reset_umask)481*b0d29bc4SBrooks Davis ATF_TEST_CASE_BODY(isolate_child__reset_umask)
482*b0d29bc4SBrooks Davis {
483*b0d29bc4SBrooks Davis const process::status status = fork_and_run(check_umask);
484*b0d29bc4SBrooks Davis ATF_REQUIRE(status.exited());
485*b0d29bc4SBrooks Davis ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus());
486*b0d29bc4SBrooks Davis }
487*b0d29bc4SBrooks Davis
488*b0d29bc4SBrooks Davis
489*b0d29bc4SBrooks Davis /// Executes isolate_path() and compares the on-disk changes to expected values.
490*b0d29bc4SBrooks Davis ///
491*b0d29bc4SBrooks Davis /// \param unprivileged_user The user to pass to isolate_path; may be none.
492*b0d29bc4SBrooks Davis /// \param exp_uid Expected UID or none to expect the old value.
493*b0d29bc4SBrooks Davis /// \param exp_gid Expected GID or none to expect the old value.
494*b0d29bc4SBrooks Davis static void
do_isolate_path_test(const optional<passwd::user> & unprivileged_user,const optional<uid_t> & exp_uid,const optional<gid_t> & exp_gid)495*b0d29bc4SBrooks Davis do_isolate_path_test(const optional< passwd::user >& unprivileged_user,
496*b0d29bc4SBrooks Davis const optional< uid_t >& exp_uid,
497*b0d29bc4SBrooks Davis const optional< gid_t >& exp_gid)
498*b0d29bc4SBrooks Davis {
499*b0d29bc4SBrooks Davis const fs::path dir("dir");
500*b0d29bc4SBrooks Davis fs::mkdir(dir, 0755);
501*b0d29bc4SBrooks Davis struct ::stat old_sb;
502*b0d29bc4SBrooks Davis ATF_REQUIRE(::stat(dir.c_str(), &old_sb) != -1);
503*b0d29bc4SBrooks Davis
504*b0d29bc4SBrooks Davis process::isolate_path(unprivileged_user, dir);
505*b0d29bc4SBrooks Davis
506*b0d29bc4SBrooks Davis struct ::stat new_sb;
507*b0d29bc4SBrooks Davis ATF_REQUIRE(::stat(dir.c_str(), &new_sb) != -1);
508*b0d29bc4SBrooks Davis
509*b0d29bc4SBrooks Davis if (exp_uid)
510*b0d29bc4SBrooks Davis ATF_REQUIRE_EQ(exp_uid.get(), new_sb.st_uid);
511*b0d29bc4SBrooks Davis else
512*b0d29bc4SBrooks Davis ATF_REQUIRE_EQ(old_sb.st_uid, new_sb.st_uid);
513*b0d29bc4SBrooks Davis
514*b0d29bc4SBrooks Davis if (exp_gid)
515*b0d29bc4SBrooks Davis ATF_REQUIRE_EQ(exp_gid.get(), new_sb.st_gid);
516*b0d29bc4SBrooks Davis else
517*b0d29bc4SBrooks Davis ATF_REQUIRE_EQ(old_sb.st_gid, new_sb.st_gid);
518*b0d29bc4SBrooks Davis }
519*b0d29bc4SBrooks Davis
520*b0d29bc4SBrooks Davis
521*b0d29bc4SBrooks Davis ATF_TEST_CASE_WITHOUT_HEAD(isolate_path__no_user);
ATF_TEST_CASE_BODY(isolate_path__no_user)522*b0d29bc4SBrooks Davis ATF_TEST_CASE_BODY(isolate_path__no_user)
523*b0d29bc4SBrooks Davis {
524*b0d29bc4SBrooks Davis do_isolate_path_test(none, none, none);
525*b0d29bc4SBrooks Davis }
526*b0d29bc4SBrooks Davis
527*b0d29bc4SBrooks Davis
528*b0d29bc4SBrooks Davis ATF_TEST_CASE_WITHOUT_HEAD(isolate_path__same_user);
ATF_TEST_CASE_BODY(isolate_path__same_user)529*b0d29bc4SBrooks Davis ATF_TEST_CASE_BODY(isolate_path__same_user)
530*b0d29bc4SBrooks Davis {
531*b0d29bc4SBrooks Davis do_isolate_path_test(utils::make_optional(passwd::current_user()),
532*b0d29bc4SBrooks Davis none, none);
533*b0d29bc4SBrooks Davis }
534*b0d29bc4SBrooks Davis
535*b0d29bc4SBrooks Davis
536*b0d29bc4SBrooks Davis ATF_TEST_CASE(isolate_path__other_user_when_unprivileged);
ATF_TEST_CASE_HEAD(isolate_path__other_user_when_unprivileged)537*b0d29bc4SBrooks Davis ATF_TEST_CASE_HEAD(isolate_path__other_user_when_unprivileged)
538*b0d29bc4SBrooks Davis {
539*b0d29bc4SBrooks Davis set_md_var("require.user", "unprivileged");
540*b0d29bc4SBrooks Davis }
ATF_TEST_CASE_BODY(isolate_path__other_user_when_unprivileged)541*b0d29bc4SBrooks Davis ATF_TEST_CASE_BODY(isolate_path__other_user_when_unprivileged)
542*b0d29bc4SBrooks Davis {
543*b0d29bc4SBrooks Davis passwd::user user = passwd::current_user();
544*b0d29bc4SBrooks Davis user.uid += 1;
545*b0d29bc4SBrooks Davis user.gid += 1;
546*b0d29bc4SBrooks Davis
547*b0d29bc4SBrooks Davis do_isolate_path_test(utils::make_optional(user), none, none);
548*b0d29bc4SBrooks Davis }
549*b0d29bc4SBrooks Davis
550*b0d29bc4SBrooks Davis
551*b0d29bc4SBrooks Davis ATF_TEST_CASE(isolate_path__drop_privileges);
ATF_TEST_CASE_HEAD(isolate_path__drop_privileges)552*b0d29bc4SBrooks Davis ATF_TEST_CASE_HEAD(isolate_path__drop_privileges)
553*b0d29bc4SBrooks Davis {
554*b0d29bc4SBrooks Davis set_md_var("require.config", "unprivileged-user");
555*b0d29bc4SBrooks Davis set_md_var("require.user", "root");
556*b0d29bc4SBrooks Davis }
ATF_TEST_CASE_BODY(isolate_path__drop_privileges)557*b0d29bc4SBrooks Davis ATF_TEST_CASE_BODY(isolate_path__drop_privileges)
558*b0d29bc4SBrooks Davis {
559*b0d29bc4SBrooks Davis const passwd::user unprivileged_user = passwd::find_user_by_name(
560*b0d29bc4SBrooks Davis get_config_var("unprivileged-user"));
561*b0d29bc4SBrooks Davis do_isolate_path_test(utils::make_optional(unprivileged_user),
562*b0d29bc4SBrooks Davis utils::make_optional(unprivileged_user.uid),
563*b0d29bc4SBrooks Davis utils::make_optional(unprivileged_user.gid));
564*b0d29bc4SBrooks Davis }
565*b0d29bc4SBrooks Davis
566*b0d29bc4SBrooks Davis
567*b0d29bc4SBrooks Davis ATF_TEST_CASE(isolate_path__drop_privileges_only_uid);
ATF_TEST_CASE_HEAD(isolate_path__drop_privileges_only_uid)568*b0d29bc4SBrooks Davis ATF_TEST_CASE_HEAD(isolate_path__drop_privileges_only_uid)
569*b0d29bc4SBrooks Davis {
570*b0d29bc4SBrooks Davis set_md_var("require.config", "unprivileged-user");
571*b0d29bc4SBrooks Davis set_md_var("require.user", "root");
572*b0d29bc4SBrooks Davis }
ATF_TEST_CASE_BODY(isolate_path__drop_privileges_only_uid)573*b0d29bc4SBrooks Davis ATF_TEST_CASE_BODY(isolate_path__drop_privileges_only_uid)
574*b0d29bc4SBrooks Davis {
575*b0d29bc4SBrooks Davis passwd::user unprivileged_user = passwd::find_user_by_name(
576*b0d29bc4SBrooks Davis get_config_var("unprivileged-user"));
577*b0d29bc4SBrooks Davis unprivileged_user.gid = ::getgid();
578*b0d29bc4SBrooks Davis do_isolate_path_test(utils::make_optional(unprivileged_user),
579*b0d29bc4SBrooks Davis utils::make_optional(unprivileged_user.uid),
580*b0d29bc4SBrooks Davis none);
581*b0d29bc4SBrooks Davis }
582*b0d29bc4SBrooks Davis
583*b0d29bc4SBrooks Davis
584*b0d29bc4SBrooks Davis ATF_TEST_CASE(isolate_path__drop_privileges_only_gid);
ATF_TEST_CASE_HEAD(isolate_path__drop_privileges_only_gid)585*b0d29bc4SBrooks Davis ATF_TEST_CASE_HEAD(isolate_path__drop_privileges_only_gid)
586*b0d29bc4SBrooks Davis {
587*b0d29bc4SBrooks Davis set_md_var("require.config", "unprivileged-user");
588*b0d29bc4SBrooks Davis set_md_var("require.user", "root");
589*b0d29bc4SBrooks Davis }
ATF_TEST_CASE_BODY(isolate_path__drop_privileges_only_gid)590*b0d29bc4SBrooks Davis ATF_TEST_CASE_BODY(isolate_path__drop_privileges_only_gid)
591*b0d29bc4SBrooks Davis {
592*b0d29bc4SBrooks Davis passwd::user unprivileged_user = passwd::find_user_by_name(
593*b0d29bc4SBrooks Davis get_config_var("unprivileged-user"));
594*b0d29bc4SBrooks Davis unprivileged_user.uid = ::getuid();
595*b0d29bc4SBrooks Davis do_isolate_path_test(utils::make_optional(unprivileged_user),
596*b0d29bc4SBrooks Davis none,
597*b0d29bc4SBrooks Davis utils::make_optional(unprivileged_user.gid));
598*b0d29bc4SBrooks Davis }
599*b0d29bc4SBrooks Davis
600*b0d29bc4SBrooks Davis
ATF_INIT_TEST_CASES(tcs)601*b0d29bc4SBrooks Davis ATF_INIT_TEST_CASES(tcs)
602*b0d29bc4SBrooks Davis {
603*b0d29bc4SBrooks Davis ATF_ADD_TEST_CASE(tcs, isolate_child__clean_environment);
604*b0d29bc4SBrooks Davis ATF_ADD_TEST_CASE(tcs, isolate_child__other_user_when_unprivileged);
605*b0d29bc4SBrooks Davis ATF_ADD_TEST_CASE(tcs, isolate_child__drop_privileges);
606*b0d29bc4SBrooks Davis ATF_ADD_TEST_CASE(tcs, isolate_child__drop_privileges_fail_uid);
607*b0d29bc4SBrooks Davis ATF_ADD_TEST_CASE(tcs, isolate_child__drop_privileges_fail_gid);
608*b0d29bc4SBrooks Davis ATF_ADD_TEST_CASE(tcs, isolate_child__enable_core_dumps);
609*b0d29bc4SBrooks Davis ATF_ADD_TEST_CASE(tcs, isolate_child__enter_work_directory);
610*b0d29bc4SBrooks Davis ATF_ADD_TEST_CASE(tcs, isolate_child__enter_work_directory_failure);
611*b0d29bc4SBrooks Davis ATF_ADD_TEST_CASE(tcs, isolate_child__new_session);
612*b0d29bc4SBrooks Davis ATF_ADD_TEST_CASE(tcs, isolate_child__no_terminal);
613*b0d29bc4SBrooks Davis ATF_ADD_TEST_CASE(tcs, isolate_child__process_group);
614*b0d29bc4SBrooks Davis ATF_ADD_TEST_CASE(tcs, isolate_child__reset_umask);
615*b0d29bc4SBrooks Davis
616*b0d29bc4SBrooks Davis ATF_ADD_TEST_CASE(tcs, isolate_path__no_user);
617*b0d29bc4SBrooks Davis ATF_ADD_TEST_CASE(tcs, isolate_path__same_user);
618*b0d29bc4SBrooks Davis ATF_ADD_TEST_CASE(tcs, isolate_path__other_user_when_unprivileged);
619*b0d29bc4SBrooks Davis ATF_ADD_TEST_CASE(tcs, isolate_path__drop_privileges);
620*b0d29bc4SBrooks Davis ATF_ADD_TEST_CASE(tcs, isolate_path__drop_privileges_only_uid);
621*b0d29bc4SBrooks Davis ATF_ADD_TEST_CASE(tcs, isolate_path__drop_privileges_only_gid);
622*b0d29bc4SBrooks Davis }
623