1 /*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "fd_utils.h"
18
19 #include <algorithm>
20
21 #include <fcntl.h>
22 #include <grp.h>
23 #include <stdlib.h>
24 #include <sys/socket.h>
25 #include <sys/types.h>
26 #include <sys/un.h>
27 #include <unistd.h>
28
29 #include <android-base/file.h>
30 #include <android-base/logging.h>
31 #include <android-base/stringprintf.h>
32 #include <android-base/strings.h>
33
34 // Static whitelist of open paths that the zygote is allowed to keep open.
35 static const char* kPathWhitelist[] = {
36 "/apex/com.android.conscrypt/javalib/conscrypt.jar",
37 "/apex/com.android.media/javalib/updatable-media.jar",
38 "/dev/null",
39 "/dev/socket/zygote",
40 "/dev/socket/zygote_secondary",
41 "/dev/socket/usap_pool_primary",
42 "/dev/socket/usap_pool_secondary",
43 "/dev/socket/webview_zygote",
44 "/dev/socket/heapprofd",
45 "/sys/kernel/debug/tracing/trace_marker",
46 "/system/framework/framework-res.apk",
47 "/dev/urandom",
48 "/dev/ion",
49 "/dev/dri/renderD129", // Fixes b/31172436
50 };
51
52 static const char kFdPath[] = "/proc/self/fd";
53
54 // static
Get()55 FileDescriptorWhitelist* FileDescriptorWhitelist::Get() {
56 if (instance_ == nullptr) {
57 instance_ = new FileDescriptorWhitelist();
58 }
59 return instance_;
60 }
61
IsAllowed(const std::string & path) const62 bool FileDescriptorWhitelist::IsAllowed(const std::string& path) const {
63 // Check the static whitelist path.
64 for (const auto& whitelist_path : kPathWhitelist) {
65 if (path == whitelist_path)
66 return true;
67 }
68
69 // Check any paths added to the dynamic whitelist.
70 for (const auto& whitelist_path : whitelist_) {
71 if (path == whitelist_path)
72 return true;
73 }
74
75 // Framework jars are allowed.
76 static const char* kFrameworksPrefix = "/system/framework/";
77 static const char* kJarSuffix = ".jar";
78 if (android::base::StartsWith(path, kFrameworksPrefix)
79 && android::base::EndsWith(path, kJarSuffix)) {
80 return true;
81 }
82
83 // Jars from the runtime apex are allowed.
84 static const char* kRuntimeApexPrefix = "/apex/com.android.runtime/javalib/";
85 if (android::base::StartsWith(path, kRuntimeApexPrefix)
86 && android::base::EndsWith(path, kJarSuffix)) {
87 return true;
88 }
89
90 // Whitelist files needed for Runtime Resource Overlay, like these:
91 // /system/vendor/overlay/framework-res.apk
92 // /system/vendor/overlay-subdir/pg/framework-res.apk
93 // /vendor/overlay/framework-res.apk
94 // /vendor/overlay/PG/android-framework-runtime-resource-overlay.apk
95 // /data/resource-cache/system@vendor@overlay@framework-res.apk@idmap
96 // /data/resource-cache/system@vendor@overlay-subdir@pg@framework-res.apk@idmap
97 // See AssetManager.cpp for more details on overlay-subdir.
98 static const char* kOverlayDir = "/system/vendor/overlay/";
99 static const char* kVendorOverlayDir = "/vendor/overlay";
100 static const char* kVendorOverlaySubdir = "/system/vendor/overlay-subdir/";
101 static const char* kSystemProductOverlayDir = "/system/product/overlay/";
102 static const char* kProductOverlayDir = "/product/overlay";
103 static const char* kSystemProductServicesOverlayDir = "/system/product_services/overlay/";
104 static const char* kProductServicesOverlayDir = "/product_services/overlay";
105 static const char* kSystemOdmOverlayDir = "/system/odm/overlay";
106 static const char* kOdmOverlayDir = "/odm/overlay";
107 static const char* kSystemOemOverlayDir = "/system/oem/overlay";
108 static const char* kOemOverlayDir = "/oem/overlay";
109 static const char* kApkSuffix = ".apk";
110
111 if ((android::base::StartsWith(path, kOverlayDir)
112 || android::base::StartsWith(path, kVendorOverlaySubdir)
113 || android::base::StartsWith(path, kVendorOverlayDir)
114 || android::base::StartsWith(path, kSystemProductOverlayDir)
115 || android::base::StartsWith(path, kProductOverlayDir)
116 || android::base::StartsWith(path, kSystemProductServicesOverlayDir)
117 || android::base::StartsWith(path, kProductServicesOverlayDir)
118 || android::base::StartsWith(path, kSystemOdmOverlayDir)
119 || android::base::StartsWith(path, kOdmOverlayDir)
120 || android::base::StartsWith(path, kSystemOemOverlayDir)
121 || android::base::StartsWith(path, kOemOverlayDir))
122 && android::base::EndsWith(path, kApkSuffix)
123 && path.find("/../") == std::string::npos) {
124 return true;
125 }
126
127 static const char* kOverlayIdmapPrefix = "/data/resource-cache/";
128 static const char* kOverlayIdmapSuffix = ".apk@idmap";
129 if (android::base::StartsWith(path, kOverlayIdmapPrefix)
130 && android::base::EndsWith(path, kOverlayIdmapSuffix)
131 && path.find("/../") == std::string::npos) {
132 return true;
133 }
134
135 // All regular files that are placed under this path are whitelisted automatically.
136 static const char* kZygoteWhitelistPath = "/vendor/zygote_whitelist/";
137 if (android::base::StartsWith(path, kZygoteWhitelistPath)
138 && path.find("/../") == std::string::npos) {
139 return true;
140 }
141
142 return false;
143 }
144
FileDescriptorWhitelist()145 FileDescriptorWhitelist::FileDescriptorWhitelist()
146 : whitelist_() {
147 }
148
149 FileDescriptorWhitelist* FileDescriptorWhitelist::instance_ = nullptr;
150
151 // Keeps track of all relevant information (flags, offset etc.) of an
152 // open zygote file descriptor.
153 class FileDescriptorInfo {
154 public:
155 // Create a FileDescriptorInfo for a given file descriptor.
156 static FileDescriptorInfo* CreateFromFd(int fd, fail_fn_t fail_fn);
157
158 // Checks whether the file descriptor associated with this object
159 // refers to the same description.
160 bool RefersToSameFile() const;
161
162 void ReopenOrDetach(fail_fn_t fail_fn) const;
163
164 const int fd;
165 const struct stat stat;
166 const std::string file_path;
167 const int open_flags;
168 const int fd_flags;
169 const int fs_flags;
170 const off_t offset;
171 const bool is_sock;
172
173 private:
174 explicit FileDescriptorInfo(int fd);
175
176 FileDescriptorInfo(struct stat stat, const std::string& file_path, int fd, int open_flags,
177 int fd_flags, int fs_flags, off_t offset);
178
179 // Returns the locally-bound name of the socket |fd|. Returns true
180 // iff. all of the following hold :
181 //
182 // - the socket's sa_family is AF_UNIX.
183 // - the length of the path is greater than zero (i.e, not an unnamed socket).
184 // - the first byte of the path isn't zero (i.e, not a socket with an abstract
185 // address).
186 static bool GetSocketName(const int fd, std::string* result);
187
188 void DetachSocket(fail_fn_t fail_fn) const;
189
190 DISALLOW_COPY_AND_ASSIGN(FileDescriptorInfo);
191 };
192
193 // static
CreateFromFd(int fd,fail_fn_t fail_fn)194 FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, fail_fn_t fail_fn) {
195 struct stat f_stat;
196 // This should never happen; the zygote should always have the right set
197 // of permissions required to stat all its open files.
198 if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) {
199 fail_fn(android::base::StringPrintf("Unable to stat %d", fd));
200 }
201
202 const FileDescriptorWhitelist* whitelist = FileDescriptorWhitelist::Get();
203
204 if (S_ISSOCK(f_stat.st_mode)) {
205 std::string socket_name;
206 if (!GetSocketName(fd, &socket_name)) {
207 fail_fn("Unable to get socket name");
208 }
209
210 if (!whitelist->IsAllowed(socket_name)) {
211 fail_fn(android::base::StringPrintf("Socket name not whitelisted : %s (fd=%d)",
212 socket_name.c_str(),
213 fd));
214 }
215
216 return new FileDescriptorInfo(fd);
217 }
218
219 // We only handle whitelisted regular files and character devices. Whitelisted
220 // character devices must provide a guarantee of sensible behaviour when
221 // reopened.
222 //
223 // S_ISDIR : Not supported. (We could if we wanted to, but it's unused).
224 // S_ISLINK : Not supported.
225 // S_ISBLK : Not supported.
226 // S_ISFIFO : Not supported. Note that the Zygote and USAPs use pipes to
227 // communicate with the child processes across forks but those should have been
228 // added to the redirection exemption list.
229 if (!S_ISCHR(f_stat.st_mode) && !S_ISREG(f_stat.st_mode)) {
230 std::string mode = "Unknown";
231
232 if (S_ISDIR(f_stat.st_mode)) {
233 mode = "DIR";
234 } else if (S_ISLNK(f_stat.st_mode)) {
235 mode = "LINK";
236 } else if (S_ISBLK(f_stat.st_mode)) {
237 mode = "BLOCK";
238 } else if (S_ISFIFO(f_stat.st_mode)) {
239 mode = "FIFO";
240 }
241
242 fail_fn(android::base::StringPrintf("Unsupported st_mode for FD %d: %s", fd, mode.c_str()));
243 }
244
245 std::string file_path;
246 const std::string fd_path = android::base::StringPrintf("/proc/self/fd/%d", fd);
247 if (!android::base::Readlink(fd_path, &file_path)) {
248 fail_fn(android::base::StringPrintf("Could not read fd link %s: %s",
249 fd_path.c_str(),
250 strerror(errno)));
251 }
252
253 if (!whitelist->IsAllowed(file_path)) {
254 fail_fn(std::string("Not whitelisted : ").append(file_path));
255 }
256
257 // File descriptor flags : currently on FD_CLOEXEC. We can set these
258 // using F_SETFD - we're single threaded at this point of execution so
259 // there won't be any races.
260 const int fd_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD));
261 if (fd_flags == -1) {
262 fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_GETFD) (%s): %s",
263 fd,
264 file_path.c_str(),
265 strerror(errno)));
266 }
267
268 // File status flags :
269 // - File access mode : (O_RDONLY, O_WRONLY...) we'll pass these through
270 // to the open() call.
271 //
272 // - File creation flags : (O_CREAT, O_EXCL...) - there's not much we can
273 // do about these, since the file has already been created. We shall ignore
274 // them here.
275 //
276 // - Other flags : We'll have to set these via F_SETFL. On linux, F_SETFL
277 // can only set O_APPEND, O_ASYNC, O_DIRECT, O_NOATIME, and O_NONBLOCK.
278 // In particular, it can't set O_SYNC and O_DSYNC. We'll have to test for
279 // their presence and pass them in to open().
280 int fs_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFL));
281 if (fs_flags == -1) {
282 fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_GETFL) (%s): %s",
283 fd,
284 file_path.c_str(),
285 strerror(errno)));
286 }
287
288 // File offset : Ignore the offset for non seekable files.
289 const off_t offset = TEMP_FAILURE_RETRY(lseek64(fd, 0, SEEK_CUR));
290
291 // We pass the flags that open accepts to open, and use F_SETFL for
292 // the rest of them.
293 static const int kOpenFlags = (O_RDONLY | O_WRONLY | O_RDWR | O_DSYNC | O_SYNC);
294 int open_flags = fs_flags & (kOpenFlags);
295 fs_flags = fs_flags & (~(kOpenFlags));
296
297 return new FileDescriptorInfo(f_stat, file_path, fd, open_flags, fd_flags, fs_flags, offset);
298 }
299
RefersToSameFile() const300 bool FileDescriptorInfo::RefersToSameFile() const {
301 struct stat f_stat;
302 if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) {
303 PLOG(ERROR) << "Unable to restat fd " << fd;
304 return false;
305 }
306
307 return f_stat.st_ino == stat.st_ino && f_stat.st_dev == stat.st_dev;
308 }
309
ReopenOrDetach(fail_fn_t fail_fn) const310 void FileDescriptorInfo::ReopenOrDetach(fail_fn_t fail_fn) const {
311 if (is_sock) {
312 return DetachSocket(fail_fn);
313 }
314
315 // NOTE: This might happen if the file was unlinked after being opened.
316 // It's a common pattern in the case of temporary files and the like but
317 // we should not allow such usage from the zygote.
318 const int new_fd = TEMP_FAILURE_RETRY(open(file_path.c_str(), open_flags));
319
320 if (new_fd == -1) {
321 fail_fn(android::base::StringPrintf("Failed open(%s, %i): %s",
322 file_path.c_str(),
323 open_flags,
324 strerror(errno)));
325 }
326
327 if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFD, fd_flags)) == -1) {
328 close(new_fd);
329 fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_SETFD, %d) (%s): %s",
330 new_fd,
331 fd_flags,
332 file_path.c_str(),
333 strerror(errno)));
334 }
335
336 if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFL, fs_flags)) == -1) {
337 close(new_fd);
338 fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_SETFL, %d) (%s): %s",
339 new_fd,
340 fs_flags,
341 file_path.c_str(),
342 strerror(errno)));
343 }
344
345 if (offset != -1 && TEMP_FAILURE_RETRY(lseek64(new_fd, offset, SEEK_SET)) == -1) {
346 close(new_fd);
347 fail_fn(android::base::StringPrintf("Failed lseek64(%d, SEEK_SET) (%s): %s",
348 new_fd,
349 file_path.c_str(),
350 strerror(errno)));
351 }
352
353 int dup_flags = (fd_flags & FD_CLOEXEC) ? O_CLOEXEC : 0;
354 if (TEMP_FAILURE_RETRY(dup3(new_fd, fd, dup_flags)) == -1) {
355 close(new_fd);
356 fail_fn(android::base::StringPrintf("Failed dup3(%d, %d, %d) (%s): %s",
357 fd,
358 new_fd,
359 dup_flags,
360 file_path.c_str(),
361 strerror(errno)));
362 }
363
364 close(new_fd);
365 }
366
FileDescriptorInfo(int fd)367 FileDescriptorInfo::FileDescriptorInfo(int fd) :
368 fd(fd),
369 stat(),
370 open_flags(0),
371 fd_flags(0),
372 fs_flags(0),
373 offset(0),
374 is_sock(true) {
375 }
376
FileDescriptorInfo(struct stat stat,const std::string & file_path,int fd,int open_flags,int fd_flags,int fs_flags,off_t offset)377 FileDescriptorInfo::FileDescriptorInfo(struct stat stat, const std::string& file_path,
378 int fd, int open_flags, int fd_flags, int fs_flags,
379 off_t offset) :
380 fd(fd),
381 stat(stat),
382 file_path(file_path),
383 open_flags(open_flags),
384 fd_flags(fd_flags),
385 fs_flags(fs_flags),
386 offset(offset),
387 is_sock(false) {
388 }
389
GetSocketName(const int fd,std::string * result)390 bool FileDescriptorInfo::GetSocketName(const int fd, std::string* result) {
391 sockaddr_storage ss;
392 sockaddr* addr = reinterpret_cast<sockaddr*>(&ss);
393 socklen_t addr_len = sizeof(ss);
394
395 if (TEMP_FAILURE_RETRY(getsockname(fd, addr, &addr_len)) == -1) {
396 PLOG(ERROR) << "Failed getsockname(" << fd << ")";
397 return false;
398 }
399
400 if (addr->sa_family != AF_UNIX) {
401 LOG(ERROR) << "Unsupported socket (fd=" << fd << ") with family " << addr->sa_family;
402 return false;
403 }
404
405 const sockaddr_un* unix_addr = reinterpret_cast<const sockaddr_un*>(&ss);
406
407 size_t path_len = addr_len - offsetof(struct sockaddr_un, sun_path);
408 // This is an unnamed local socket, we do not accept it.
409 if (path_len == 0) {
410 LOG(ERROR) << "Unsupported AF_UNIX socket (fd=" << fd << ") with empty path.";
411 return false;
412 }
413
414 // This is a local socket with an abstract address. Remove the leading NUL byte and
415 // add a human-readable "ABSTRACT/" prefix.
416 if (unix_addr->sun_path[0] == '\0') {
417 *result = "ABSTRACT/";
418 result->append(&unix_addr->sun_path[1], path_len - 1);
419 return true;
420 }
421
422 // If we're here, sun_path must refer to a null terminated filesystem
423 // pathname (man 7 unix). Remove the terminator before assigning it to an
424 // std::string.
425 if (unix_addr->sun_path[path_len - 1] == '\0') {
426 --path_len;
427 }
428
429 result->assign(unix_addr->sun_path, path_len);
430 return true;
431 }
432
DetachSocket(fail_fn_t fail_fn) const433 void FileDescriptorInfo::DetachSocket(fail_fn_t fail_fn) const {
434 const int dev_null_fd = open("/dev/null", O_RDWR | O_CLOEXEC);
435 if (dev_null_fd < 0) {
436 fail_fn(std::string("Failed to open /dev/null: ").append(strerror(errno)));
437 }
438
439 if (dup3(dev_null_fd, fd, O_CLOEXEC) == -1) {
440 fail_fn(android::base::StringPrintf("Failed dup3 on socket descriptor %d: %s",
441 fd,
442 strerror(errno)));
443 }
444
445 if (close(dev_null_fd) == -1) {
446 fail_fn(android::base::StringPrintf("Failed close(%d): %s", dev_null_fd, strerror(errno)));
447 }
448 }
449
450 // static
Create(const std::vector<int> & fds_to_ignore,fail_fn_t fail_fn)451 FileDescriptorTable* FileDescriptorTable::Create(const std::vector<int>& fds_to_ignore,
452 fail_fn_t fail_fn) {
453 DIR* proc_fd_dir = opendir(kFdPath);
454 if (proc_fd_dir == nullptr) {
455 fail_fn(std::string("Unable to open directory ").append(kFdPath));
456 }
457
458 int dir_fd = dirfd(proc_fd_dir);
459 dirent* dir_entry;
460
461 std::unordered_map<int, FileDescriptorInfo*> open_fd_map;
462 while ((dir_entry = readdir(proc_fd_dir)) != nullptr) {
463 const int fd = ParseFd(dir_entry, dir_fd);
464 if (fd == -1) {
465 continue;
466 }
467
468 if (std::find(fds_to_ignore.begin(), fds_to_ignore.end(), fd) != fds_to_ignore.end()) {
469 continue;
470 }
471
472 open_fd_map[fd] = FileDescriptorInfo::CreateFromFd(fd, fail_fn);
473 }
474
475 if (closedir(proc_fd_dir) == -1) {
476 fail_fn("Unable to close directory");
477 }
478
479 return new FileDescriptorTable(open_fd_map);
480 }
481
Restat(const std::vector<int> & fds_to_ignore,fail_fn_t fail_fn)482 void FileDescriptorTable::Restat(const std::vector<int>& fds_to_ignore, fail_fn_t fail_fn) {
483 std::set<int> open_fds;
484
485 // First get the list of open descriptors.
486 DIR* proc_fd_dir = opendir(kFdPath);
487 if (proc_fd_dir == nullptr) {
488 fail_fn(android::base::StringPrintf("Unable to open directory %s: %s",
489 kFdPath,
490 strerror(errno)));
491 }
492
493 int dir_fd = dirfd(proc_fd_dir);
494 dirent* dir_entry;
495 while ((dir_entry = readdir(proc_fd_dir)) != nullptr) {
496 const int fd = ParseFd(dir_entry, dir_fd);
497 if (fd == -1) {
498 continue;
499 }
500
501 if (std::find(fds_to_ignore.begin(), fds_to_ignore.end(), fd) != fds_to_ignore.end()) {
502 continue;
503 }
504
505 open_fds.insert(fd);
506 }
507
508 if (closedir(proc_fd_dir) == -1) {
509 fail_fn(android::base::StringPrintf("Unable to close directory: %s", strerror(errno)));
510 }
511
512 RestatInternal(open_fds, fail_fn);
513 }
514
515 // Reopens all file descriptors that are contained in the table.
ReopenOrDetach(fail_fn_t fail_fn)516 void FileDescriptorTable::ReopenOrDetach(fail_fn_t fail_fn) {
517 std::unordered_map<int, FileDescriptorInfo*>::const_iterator it;
518 for (it = open_fd_map_.begin(); it != open_fd_map_.end(); ++it) {
519 const FileDescriptorInfo* info = it->second;
520 if (info == nullptr) {
521 return;
522 } else {
523 info->ReopenOrDetach(fail_fn);
524 }
525 }
526 }
527
FileDescriptorTable(const std::unordered_map<int,FileDescriptorInfo * > & map)528 FileDescriptorTable::FileDescriptorTable(
529 const std::unordered_map<int, FileDescriptorInfo*>& map)
530 : open_fd_map_(map) {
531 }
532
RestatInternal(std::set<int> & open_fds,fail_fn_t fail_fn)533 void FileDescriptorTable::RestatInternal(std::set<int>& open_fds, fail_fn_t fail_fn) {
534 // Iterate through the list of file descriptors we've already recorded
535 // and check whether :
536 //
537 // (a) they continue to be open.
538 // (b) they refer to the same file.
539 //
540 // We'll only store the last error message.
541 std::unordered_map<int, FileDescriptorInfo*>::iterator it = open_fd_map_.begin();
542 while (it != open_fd_map_.end()) {
543 std::set<int>::const_iterator element = open_fds.find(it->first);
544 if (element == open_fds.end()) {
545 // The entry from the file descriptor table is no longer in the list
546 // of open files. We warn about this condition and remove it from
547 // the list of FDs under consideration.
548 //
549 // TODO(narayan): This will be an error in a future android release.
550 // error = true;
551 // ALOGW("Zygote closed file descriptor %d.", it->first);
552 it = open_fd_map_.erase(it);
553 } else {
554 // The entry from the file descriptor table is still open. Restat
555 // it and check whether it refers to the same file.
556 if (!it->second->RefersToSameFile()) {
557 // The file descriptor refers to a different description. We must
558 // update our entry in the table.
559 delete it->second;
560 it->second = FileDescriptorInfo::CreateFromFd(*element, fail_fn);
561 } else {
562 // It's the same file. Nothing to do here. Move on to the next open
563 // FD.
564 }
565
566 ++it;
567
568 // Finally, remove the FD from the set of open_fds. We do this last because
569 // |element| will not remain valid after a call to erase.
570 open_fds.erase(element);
571 }
572 }
573
574 if (open_fds.size() > 0) {
575 // The zygote has opened new file descriptors since our last inspection.
576 // We warn about this condition and add them to our table.
577 //
578 // TODO(narayan): This will be an error in a future android release.
579 // error = true;
580 // ALOGW("Zygote opened %zd new file descriptor(s).", open_fds.size());
581
582 // TODO(narayan): This code will be removed in a future android release.
583 std::set<int>::const_iterator it;
584 for (it = open_fds.begin(); it != open_fds.end(); ++it) {
585 const int fd = (*it);
586 open_fd_map_[fd] = FileDescriptorInfo::CreateFromFd(fd, fail_fn);
587 }
588 }
589 }
590
591 // static
ParseFd(dirent * dir_entry,int dir_fd)592 int FileDescriptorTable::ParseFd(dirent* dir_entry, int dir_fd) {
593 char* end;
594 const int fd = strtol(dir_entry->d_name, &end, 10);
595 if ((*end) != '\0') {
596 return -1;
597 }
598
599 // Don't bother with the standard input/output/error, they're handled
600 // specially post-fork anyway.
601 if (fd <= STDERR_FILENO || fd == dir_fd) {
602 return -1;
603 }
604
605 return fd;
606 }
607