blob: 099362cf53a5d31cc972e2ae4aa70d6bb6508b40 [file] [log] [blame]
Vernon Mauery0d0cd162020-01-31 10:04:10 -08001#include <algorithm>
2#include <array>
3#include <ipmi-whitelist.hpp>
4#include <ipmid/api.hpp>
5#include <ipmid/utils.hpp>
6#include <phosphor-logging/elog-errors.hpp>
7#include <phosphor-logging/log.hpp>
8#include <xyz/openbmc_project/Control/Security/RestrictionMode/server.hpp>
9
10using namespace phosphor::logging;
11using namespace sdbusplus::xyz::openbmc_project::Common::Error;
12using namespace sdbusplus::xyz::openbmc_project::Control::Security::server;
13
14namespace ipmi
15{
16
17// put the filter provider in an unnamed namespace
18namespace
19{
20
21/** @class WhitelistFilter
22 *
23 * Class that implements an IPMI message filter based
24 * on incoming interface and a restriction mode setting
25 */
26class WhitelistFilter
27{
28
29 public:
30 WhitelistFilter();
31 ~WhitelistFilter() = default;
32 WhitelistFilter(WhitelistFilter const&) = delete;
33 WhitelistFilter(WhitelistFilter&&) = delete;
34 WhitelistFilter& operator=(WhitelistFilter const&) = delete;
35 WhitelistFilter& operator=(WhitelistFilter&&) = delete;
36
37 private:
38 void postInit();
39 void cacheRestrictedAndPostCompleteMode();
40 void handleRestrictedModeChange(sdbusplus::message::message& m);
41 void handlePostCompleteChange(sdbusplus::message::message& m);
42 void updatePostComplete(const std::string& value);
43 void updateRestrictionMode(const std::string& value);
44 ipmi::Cc filterMessage(ipmi::message::Request::ptr request);
45
46 // the BMC KCS Policy Control Modes document uses different names
47 // than the RestrictionModes D-Bus interface; use aliases
48 static constexpr RestrictionMode::Modes restrictionModeAllowAll =
49 RestrictionMode::Modes::Provisioning;
50 static constexpr RestrictionMode::Modes restrictionModeRestricted =
51 RestrictionMode::Modes::ProvisionedHostWhitelist;
52 static constexpr RestrictionMode::Modes restrictionModeDenyAll =
53 RestrictionMode::Modes::ProvisionedHostDisabled;
54
55 RestrictionMode::Modes restrictionMode = restrictionModeRestricted;
56 bool postCompleted = false;
57 std::shared_ptr<sdbusplus::asio::connection> bus;
58 std::unique_ptr<sdbusplus::bus::match::match> modeChangeMatch;
59 std::unique_ptr<sdbusplus::bus::match::match> modeIntfAddedMatch;
60 std::unique_ptr<sdbusplus::bus::match::match> postCompleteMatch;
61 std::unique_ptr<sdbusplus::bus::match::match> postCompleteIntfAddedMatch;
62
63 static constexpr const char restrictionModeIntf[] =
64 "xyz.openbmc_project.Control.Security.RestrictionMode";
65 static constexpr const char* systemOsStatusIntf =
66 "xyz.openbmc_project.State.OperatingSystem.Status";
67};
68
69WhitelistFilter::WhitelistFilter()
70{
71 bus = getSdBus();
72
73 log<level::INFO>("Loading whitelist filter");
74
75 ipmi::registerFilter(ipmi::prioOpenBmcBase,
76 [this](ipmi::message::Request::ptr request) {
77 return filterMessage(request);
78 });
79
80 // wait until io->run is going to fetch RestrictionMode
81 post_work([this]() { postInit(); });
82}
83
84void WhitelistFilter::cacheRestrictedAndPostCompleteMode()
85{
86 std::string restrictionModePath;
87 std::string restrictionModeService;
88 std::string systemOsStatusPath;
89 std::string systemOsStatusService;
90 try
91 {
92 ipmi::DbusObjectInfo restrictionObj =
93 ipmi::getDbusObject(*bus, restrictionModeIntf);
94
95 restrictionModePath = restrictionObj.first;
96 restrictionModeService = restrictionObj.second;
97
98 ipmi::DbusObjectInfo postCompleteObj =
99 ipmi::getDbusObject(*bus, systemOsStatusIntf);
100
101 systemOsStatusPath = postCompleteObj.first;
102 systemOsStatusService = postCompleteObj.second;
103 }
104 catch (const std::exception&)
105 {
106 log<level::ERR>(
107 "Could not initialize provisioning mode, defaulting to restricted",
108 entry("VALUE=%d", static_cast<int>(restrictionMode)));
109 return;
110 }
111
112 bus->async_method_call(
113 [this](boost::system::error_code ec, ipmi::Value v) {
114 if (ec)
115 {
116 log<level::ERR>(
117 "Could not initialize provisioning mode, "
118 "defaulting to restricted",
119 entry("VALUE=%d", static_cast<int>(restrictionMode)));
120 return;
121 }
122 auto mode = std::get<std::string>(v);
123 restrictionMode = RestrictionMode::convertModesFromString(mode);
124 log<level::INFO>(
125 "Read restriction mode",
126 entry("VALUE=%d", static_cast<int>(restrictionMode)));
127 },
128 restrictionModeService, restrictionModePath,
129 "org.freedesktop.DBus.Properties", "Get", restrictionModeIntf,
130 "RestrictionMode");
131
132 bus->async_method_call(
133 [this](boost::system::error_code ec, const ipmi::Value& v) {
134 if (ec)
135 {
136 log<level::ERR>("Error in OperatingSystemState Get");
137 postCompleted = true;
138 return;
139 }
140 auto value = std::get<std::string>(v);
141 if (value == "Standby")
142 {
143 postCompleted = true;
144 }
145 else
146 {
147 postCompleted = false;
148 }
149 log<level::INFO>("Read POST complete value",
150 entry("VALUE=%d", postCompleted));
151 },
152 systemOsStatusService, systemOsStatusPath,
153 "org.freedesktop.DBus.Properties", "Get", systemOsStatusIntf,
154 "OperatingSystemState");
155}
156
157void WhitelistFilter::updateRestrictionMode(const std::string& value)
158{
159 restrictionMode = RestrictionMode::convertModesFromString(value);
160 log<level::INFO>("Updated restriction mode",
161 entry("VALUE=%d", static_cast<int>(restrictionMode)));
162}
163
164void WhitelistFilter::handleRestrictedModeChange(sdbusplus::message::message& m)
165{
166 std::string signal = m.get_member();
167 if (signal == "PropertiesChanged")
168 {
169 std::string intf;
170 std::vector<std::pair<std::string, ipmi::Value>> propertyList;
171 m.read(intf, propertyList);
172 for (const auto& property : propertyList)
173 {
174 if (property.first == "RestrictionMode")
175 {
176 updateRestrictionMode(std::get<std::string>(property.second));
177 }
178 }
179 }
180 else if (signal == "InterfacesAdded")
181 {
182 sdbusplus::message::object_path path;
183 DbusInterfaceMap restModeObj;
184 m.read(path, restModeObj);
185 auto intfItr = restModeObj.find(restrictionModeIntf);
186 if (intfItr == restModeObj.end())
187 {
188 return;
189 }
190 PropertyMap& propertyList = intfItr->second;
191 auto itr = propertyList.find("RestrictionMode");
192 if (itr == propertyList.end())
193 {
194 return;
195 }
196 updateRestrictionMode(std::get<std::string>(itr->second));
197 }
198}
199
200void WhitelistFilter::updatePostComplete(const std::string& value)
201{
202 if (value == "Standby")
203 {
204 postCompleted = true;
205 }
206 else
207 {
208 postCompleted = false;
209 }
210 log<level::INFO>(postCompleted ? "Updated to POST Complete"
211 : "Updated to !POST Complete");
212}
213
214void WhitelistFilter::handlePostCompleteChange(sdbusplus::message::message& m)
215{
216 std::string signal = m.get_member();
217 if (signal == "PropertiesChanged")
218 {
219 std::string intf;
220 std::vector<std::pair<std::string, ipmi::Value>> propertyList;
221 m.read(intf, propertyList);
222 for (const auto& property : propertyList)
223 {
224 if (property.first == "OperatingSystemState")
225 {
226 updatePostComplete(std::get<std::string>(property.second));
227 }
228 }
229 }
230 else if (signal == "InterfacesAdded")
231 {
232 sdbusplus::message::object_path path;
233 DbusInterfaceMap postCompleteObj;
234 m.read(path, postCompleteObj);
235 auto intfItr = postCompleteObj.find(systemOsStatusIntf);
236 if (intfItr == postCompleteObj.end())
237 {
238 return;
239 }
240 PropertyMap& propertyList = intfItr->second;
241 auto itr = propertyList.find("OperatingSystemState");
242 if (itr == propertyList.end())
243 {
244 return;
245 }
246 updatePostComplete(std::get<std::string>(itr->second));
247 }
248}
249void WhitelistFilter::postInit()
250{
251 // Wait for changes on Restricted mode
252 namespace rules = sdbusplus::bus::match::rules;
253 const std::string filterStrModeChange =
254 rules::type::signal() + rules::member("PropertiesChanged") +
255 rules::interface("org.freedesktop.DBus.Properties") +
256 rules::argN(0, restrictionModeIntf);
257
258 const std::string filterStrModeIntfAdd =
259 rules::interfacesAdded() +
260 rules::argNpath(
261 0, "/xyz/openbmc_project/control/security/restriction_mode");
262
263 const std::string filterStrPostComplete =
264 rules::type::signal() + rules::member("PropertiesChanged") +
265 rules::interface("org.freedesktop.DBus.Properties") +
266 rules::argN(0, systemOsStatusIntf);
267
268 const std::string filterStrPostIntfAdd =
269 rules::interfacesAdded() +
270 rules::argNpath(0, "/xyz/openbmc_project/state/os");
271
272 modeChangeMatch = std::make_unique<sdbusplus::bus::match::match>(
273 *bus, filterStrModeChange, [this](sdbusplus::message::message& m) {
274 handleRestrictedModeChange(m);
275 });
276 modeIntfAddedMatch = std::make_unique<sdbusplus::bus::match::match>(
277 *bus, filterStrModeIntfAdd, [this](sdbusplus::message::message& m) {
278 handleRestrictedModeChange(m);
279 });
280
281 postCompleteMatch = std::make_unique<sdbusplus::bus::match::match>(
282 *bus, filterStrPostComplete, [this](sdbusplus::message::message& m) {
283 handlePostCompleteChange(m);
284 });
285
286 postCompleteIntfAddedMatch = std::make_unique<sdbusplus::bus::match::match>(
287 *bus, filterStrPostIntfAdd, [this](sdbusplus::message::message& m) {
288 handlePostCompleteChange(m);
289 });
290
291 // Initialize restricted mode
292 cacheRestrictedAndPostCompleteMode();
293}
294
295ipmi::Cc WhitelistFilter::filterMessage(ipmi::message::Request::ptr request)
296{
297 auto channelMask = static_cast<unsigned short>(1 << request->ctx->channel);
298 bool whitelisted = std::binary_search(
299 whitelist.cbegin(), whitelist.cend(),
300 std::make_tuple(request->ctx->netFn, request->ctx->cmd, channelMask),
301 [](const netfncmd_tuple& first, const netfncmd_tuple& value) {
302 return (std::get<2>(first) & std::get<2>(value))
303 ? first < std::make_tuple(std::get<0>(value),
304 std::get<1>(value),
305 std::get<2>(first))
306 : first < value;
307 });
308
309 // no special handling for non-system-interface channels
310 if (request->ctx->channel != ipmi::channelSystemIface)
311 {
312 if (!whitelisted)
313 {
314 log<level::INFO>("Channel/NetFn/Cmd not whitelisted",
315 entry("CHANNEL=0x%X", request->ctx->channel),
316 entry("NETFN=0x%X", int(request->ctx->netFn)),
317 entry("CMD=0x%X", int(request->ctx->cmd)));
318 return ipmi::ccCommandNotAvailable;
319 }
320 return ipmi::ccSuccess;
321 }
322
323 // for system interface, filtering is done as follows:
324 // Allow All: preboot ? ccSuccess : ccSuccess
325 // Restricted: preboot ? ccSuccess :
326 // ( whitelist ? ccSuccess : // ccCommandNotAvailable )
327 // Deny All: preboot ? ccSuccess : ccCommandNotAvailable
328
329 if (!postCompleted)
330 {
331 // Allow all commands, till POST is not completed
332 return ipmi::ccSuccess;
333 }
334
335 switch (restrictionMode)
336 {
337 case RestrictionMode::Modes::None:
338 case restrictionModeAllowAll:
339 {
340 // Allow All
341 return ipmi::ccSuccess;
342 break;
343 }
344 case restrictionModeRestricted:
345 {
346 // Restricted - follow whitelist
347 break;
348 }
349 case restrictionModeDenyAll:
350 {
351 // Deny All
352 whitelisted = false;
353 break;
354 }
355 default: // for whitelist and blacklist
356 return ipmi::ccCommandNotAvailable;
357 }
358
359 if (!whitelisted)
360 {
361 log<level::INFO>("Channel/NetFn/Cmd not whitelisted",
362 entry("CHANNEL=0x%X", request->ctx->channel),
363 entry("NETFN=0x%X", int(request->ctx->netFn)),
364 entry("CMD=0x%X", int(request->ctx->cmd)));
365 return ipmi::ccCommandNotAvailable;
366 }
367 return ipmi::ccSuccess;
368} // namespace
369
370// instantiate the WhitelistFilter when this shared object is loaded
371WhitelistFilter whitelistFilter;
372
373} // namespace
374
375} // namespace ipmi