Line data Source code
1 : // Copyright (c) 2011-2022 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/
4 : // contact@m2osw.com
5 : //
6 : // This program is free software; you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation; either version 2 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program; if not, write to the Free Software
18 : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 :
20 : // self
21 : //
22 : #include "./file_inheritance.h"
23 :
24 :
25 : // C++ includes
26 : //
27 : #include <fstream>
28 : #include <iostream>
29 : #include <list>
30 : #include <memory>
31 :
32 :
33 : // C lib includes
34 : //
35 : #include <dirent.h>
36 : #include <string.h>
37 : #include <unistd.h>
38 :
39 :
40 : /** \file
41 : * \brief Implementation of a test of the files inherited.
42 : *
43 : * Most processes are expected to only inherit stdin, stdout, and stderr.
44 : * This mimplementation verifies that this is the case.
45 : */
46 :
47 :
48 :
49 : namespace libexcept
50 : {
51 :
52 :
53 :
54 : /** \brief Load the command line of the specified process.
55 : *
56 : * This function loads the cmdline file of the specified process. If an
57 : * error occurs, the function returns an empty string. Some processes do
58 : * not have a command line.
59 : *
60 : * If your program can include cppprocess (see eventdispatcher), then I
61 : * suggest you use the cppprocess::process_info class instead. It is
62 : * much more powerful.
63 : *
64 : * \param[in] pid The process identifier.
65 : *
66 : * \return The command line of the \p pid process.
67 : */
68 1 : std::string get_command_line(pid_t pid)
69 : {
70 2 : std::string filename("/proc/");
71 1 : filename += std::to_string(pid);
72 1 : filename += "/cmdline";
73 2 : std::ifstream cmdline(filename);
74 1 : std::string line;
75 1 : std::getline(cmdline, line, '\0');
76 2 : return line;
77 : }
78 :
79 : /** \brief Check the list of files opened in this process.
80 : *
81 : * This function reads the /proc/<pid>/fd directory. If it finds files other
82 : * than 0, 1, 2, then it generates an error.
83 : *
84 : * In Debug mode, the error is fatal (it throws).
85 : *
86 : * In Release mode, the error is _just_ a warning so the process still starts
87 : * but you still get a message letting you know there may be something fishy
88 : * going on.
89 : *
90 : * In case your application actually accepts additional streams, you can
91 : * add them to the \p allowed set of file descriptors.
92 : *
93 : * \param[in] allowed Additional allowed input streams.
94 : */
95 0 : void verify_inherited_files(allowed_fds_t allowed)
96 : {
97 0 : auto closedir = [](DIR * d)
98 : {
99 0 : ::closedir(d);
100 0 : };
101 :
102 0 : int errcnt(0);
103 :
104 0 : std::string path("/proc/");
105 0 : path += std::to_string(getpid());
106 0 : path += "/fd";
107 0 : DIR * d(opendir(path.c_str()));
108 0 : std::unique_ptr<DIR, decltype(closedir)> auto_close(d, closedir);
109 :
110 : for(;;)
111 : {
112 0 : dirent const * ent(readdir(d));
113 0 : if(ent == nullptr)
114 : {
115 0 : break;
116 : }
117 :
118 0 : char const * basename(ent->d_name);
119 0 : if(basename[0] == '.')
120 : {
121 : // ignore all hidden files
122 0 : continue;
123 : }
124 :
125 0 : if(basename[0] >= '0'
126 0 : && basename[0] <= '2'
127 0 : && basename[1] == '\0')
128 : {
129 : // skip stdin, stdout, stderr
130 0 : continue;
131 : }
132 :
133 0 : int const fd(std::atoi(basename));
134 0 : if(fd == dirfd(d))
135 : {
136 : // ignore the opendir() file descriptor
137 : // (it is currently open since we are reading the directory)
138 : //
139 0 : continue;
140 : }
141 :
142 0 : if(allowed.find(fd) == allowed.end())
143 : {
144 0 : char link[256];
145 0 : ssize_t const l(readlink((path + '/' + basename).c_str(), link, sizeof(link) - 1));
146 0 : if(l <= 0)
147 : {
148 0 : link[0] = '\0';
149 : }
150 : else
151 : {
152 0 : link[l] = '\0';
153 : }
154 : std::cerr
155 0 : << "warning: file descriptor "
156 : << fd
157 : << " ("
158 : << link
159 0 : << ") leaked on invocation. Parent PID "
160 : << getppid()
161 : << ": "
162 0 : << get_command_line(getppid())
163 0 : << '\n';
164 0 : ++errcnt;
165 : }
166 0 : }
167 :
168 : #ifdef _DEBUG
169 0 : if(errcnt > 0)
170 : {
171 : // this is a logic error
172 : //
173 0 : throw file_inherited("found unexpected file descriptor leaks.");
174 : }
175 : #endif
176 0 : }
177 :
178 :
179 :
180 6 : }
181 : // namespace libexcept
182 : // vim: ts=4 sw=4 et
|