file_descriptor_set_posix.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ipc/file_descriptor_set_posix.h"
6
7#include <sys/types.h>
8#include <sys/stat.h>
9#include <unistd.h>
10
11#include "base/logging.h"
12#include "base/posix/eintr_wrapper.h"
13
14FileDescriptorSet::FileDescriptorSet()
15    : consumed_descriptor_highwater_(0) {
16}
17
18FileDescriptorSet::~FileDescriptorSet() {
19  if (consumed_descriptor_highwater_ == size())
20    return;
21
22  // We close all the owning descriptors. If this message should have
23  // been transmitted, then closing those with close flags set mirrors
24  // the expected behaviour.
25  //
26  // If this message was received with more descriptors than expected
27  // (which could a DOS against the browser by a rogue renderer) then all
28  // the descriptors have their close flag set and we free all the extra
29  // kernel resources.
30  LOG(WARNING) << "FileDescriptorSet destroyed with unconsumed descriptors: "
31               << consumed_descriptor_highwater_ << "/" << size();
32}
33
34bool FileDescriptorSet::AddToBorrow(base::PlatformFile fd) {
35  DCHECK_EQ(consumed_descriptor_highwater_, 0u);
36
37  if (size() == kMaxDescriptorsPerMessage) {
38    DLOG(WARNING) << "Cannot add file descriptor. FileDescriptorSet full.";
39    return false;
40  }
41
42  descriptors_.push_back(fd);
43  return true;
44}
45
46bool FileDescriptorSet::AddToOwn(base::ScopedFD fd) {
47  DCHECK_EQ(consumed_descriptor_highwater_, 0u);
48
49  if (size() == kMaxDescriptorsPerMessage) {
50    DLOG(WARNING) << "Cannot add file descriptor. FileDescriptorSet full.";
51    return false;
52  }
53
54  descriptors_.push_back(fd.get());
55  owned_descriptors_.push_back(new base::ScopedFD(fd.Pass()));
56  DCHECK(size() <= kMaxDescriptorsPerMessage);
57  return true;
58}
59
60base::PlatformFile FileDescriptorSet::TakeDescriptorAt(unsigned index) {
61  if (index >= size()) {
62    DLOG(WARNING) << "Accessing out of bound index:"
63                  << index << "/" << size();
64    return -1;
65  }
66
67
68  // We should always walk the descriptors in order, so it's reasonable to
69  // enforce this. Consider the case where a compromised renderer sends us
70  // the following message:
71  //
72  //   ExampleMsg:
73  //     num_fds:2 msg:FD(index = 1) control:SCM_RIGHTS {n, m}
74  //
75  // Here the renderer sent us a message which should have a descriptor, but
76  // actually sent two in an attempt to fill our fd table and kill us. By
77  // setting the index of the descriptor in the message to 1 (it should be
78  // 0), we would record a highwater of 1 and then consider all the
79  // descriptors to have been used.
80  //
81  // So we can either track of the use of each descriptor in a bitset, or we
82  // can enforce that we walk the indexes strictly in order.
83  //
84  // There's one more wrinkle: When logging messages, we may reparse them. So
85  // we have an exception: When the consumed_descriptor_highwater_ is at the
86  // end of the array and index 0 is requested, we reset the highwater value.
87  // TODO(morrita): This is absurd. This "wringle" disallow to introduce clearer
88  // ownership model. Only client is NaclIPCAdapter. See crbug.com/415294
89  if (index == 0 && consumed_descriptor_highwater_ == descriptors_.size())
90    consumed_descriptor_highwater_ = 0;
91
92  if (index != consumed_descriptor_highwater_)
93    return -1;
94
95  consumed_descriptor_highwater_ = index + 1;
96
97  base::PlatformFile file = descriptors_[index];
98
99  // TODO(morrita): In production, descriptors_.size() should be same as
100  // owned_descriptors_.size() as all read descriptors are owned by Message.
101  // We have to do this because unit test breaks this assumption. It should be
102  // changed to exercise with own-able descriptors.
103  for (ScopedVector<base::ScopedFD>::const_iterator i =
104           owned_descriptors_.begin();
105       i != owned_descriptors_.end();
106       ++i) {
107    if ((*i)->get() == file) {
108      ignore_result((*i)->release());
109      break;
110    }
111  }
112
113  return file;
114}
115
116void FileDescriptorSet::PeekDescriptors(base::PlatformFile* buffer) const {
117  std::copy(descriptors_.begin(), descriptors_.end(), buffer);
118}
119
120bool FileDescriptorSet::ContainsDirectoryDescriptor() const {
121  struct stat st;
122
123  for (std::vector<base::PlatformFile>::const_iterator i = descriptors_.begin();
124       i != descriptors_.end();
125       ++i) {
126    if (fstat(*i, &st) == 0 && S_ISDIR(st.st_mode))
127      return true;
128  }
129
130  return false;
131}
132
133void FileDescriptorSet::CommitAll() {
134  descriptors_.clear();
135  owned_descriptors_.clear();
136  consumed_descriptor_highwater_ = 0;
137}
138
139void FileDescriptorSet::ReleaseFDsToClose(
140    std::vector<base::PlatformFile>* fds) {
141  for (ScopedVector<base::ScopedFD>::iterator i = owned_descriptors_.begin();
142       i != owned_descriptors_.end();
143       ++i) {
144    fds->push_back((*i)->release());
145  }
146
147  CommitAll();
148}
149
150void FileDescriptorSet::AddDescriptorsToOwn(const base::PlatformFile* buffer,
151                                            unsigned count) {
152  DCHECK(count <= kMaxDescriptorsPerMessage);
153  DCHECK_EQ(size(), 0u);
154  DCHECK_EQ(consumed_descriptor_highwater_, 0u);
155
156  descriptors_.reserve(count);
157  owned_descriptors_.reserve(count);
158  for (unsigned i = 0; i < count; ++i) {
159    descriptors_.push_back(buffer[i]);
160    owned_descriptors_.push_back(new base::ScopedFD(buffer[i]));
161  }
162}
163