blob: 3f30a41137d9d238132734c1fca2e7793cf4f286 [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);
Arun P. Mohanana4cea1a2020-03-18 16:01:57 +053045 void handleCoreBiosDoneChange(sdbusplus::message::message& m);
46 void cacheCoreBiosDone();
Vernon Mauery0d0cd162020-01-31 10:04:10 -080047
48 // the BMC KCS Policy Control Modes document uses different names
49 // than the RestrictionModes D-Bus interface; use aliases
50 static constexpr RestrictionMode::Modes restrictionModeAllowAll =
51 RestrictionMode::Modes::Provisioning;
52 static constexpr RestrictionMode::Modes restrictionModeRestricted =
53 RestrictionMode::Modes::ProvisionedHostWhitelist;
54 static constexpr RestrictionMode::Modes restrictionModeDenyAll =
55 RestrictionMode::Modes::ProvisionedHostDisabled;
56
57 RestrictionMode::Modes restrictionMode = restrictionModeRestricted;
Arun P. Mohanana4cea1a2020-03-18 16:01:57 +053058 bool postCompleted = true;
59 bool coreBIOSDone = true;
arun-pm849c3192020-02-12 18:10:32 +053060 int channelSMM = -1;
Vernon Mauery0d0cd162020-01-31 10:04:10 -080061 std::shared_ptr<sdbusplus::asio::connection> bus;
62 std::unique_ptr<sdbusplus::bus::match::match> modeChangeMatch;
63 std::unique_ptr<sdbusplus::bus::match::match> modeIntfAddedMatch;
64 std::unique_ptr<sdbusplus::bus::match::match> postCompleteMatch;
65 std::unique_ptr<sdbusplus::bus::match::match> postCompleteIntfAddedMatch;
Arun P. Mohanana4cea1a2020-03-18 16:01:57 +053066 std::unique_ptr<sdbusplus::bus::match::match> platStateChangeMatch;
67 std::unique_ptr<sdbusplus::bus::match::match> platStateIntfAddedMatch;
Vernon Mauery0d0cd162020-01-31 10:04:10 -080068
69 static constexpr const char restrictionModeIntf[] =
70 "xyz.openbmc_project.Control.Security.RestrictionMode";
71 static constexpr const char* systemOsStatusIntf =
72 "xyz.openbmc_project.State.OperatingSystem.Status";
Arun P. Mohanana4cea1a2020-03-18 16:01:57 +053073 static constexpr const char* hostMiscIntf =
74 "xyz.openbmc_project.State.Host.Misc";
Vernon Mauery0d0cd162020-01-31 10:04:10 -080075};
76
arun-pm849c3192020-02-12 18:10:32 +053077static inline uint8_t getSMMChannel()
78{
79 ipmi::ChannelInfo chInfo;
80
81 for (int channel = 0; channel < ipmi::maxIpmiChannels; channel++)
82 {
83 if (ipmi::getChannelInfo(channel, chInfo) != ipmi::ccSuccess)
84 {
85 continue;
86 }
87
88 if (static_cast<ipmi::EChannelMediumType>(chInfo.mediumType) ==
89 ipmi::EChannelMediumType::systemInterface &&
90 channel != ipmi::channelSystemIface)
91 {
92 log<level::INFO>("SMM channel number",
93 entry("CHANNEL=%d", channel));
94 return channel;
95 }
96 }
97 log<level::ERR>("Unable to find SMM Channel Info");
98 return -1;
99}
100
Vernon Mauery0d0cd162020-01-31 10:04:10 -0800101WhitelistFilter::WhitelistFilter()
102{
103 bus = getSdBus();
104
105 log<level::INFO>("Loading whitelist filter");
106
107 ipmi::registerFilter(ipmi::prioOpenBmcBase,
108 [this](ipmi::message::Request::ptr request) {
109 return filterMessage(request);
110 });
111
arun-pm849c3192020-02-12 18:10:32 +0530112 channelSMM = getSMMChannel();
Vernon Mauery0d0cd162020-01-31 10:04:10 -0800113 // wait until io->run is going to fetch RestrictionMode
114 post_work([this]() { postInit(); });
115}
116
117void WhitelistFilter::cacheRestrictedAndPostCompleteMode()
118{
119 std::string restrictionModePath;
120 std::string restrictionModeService;
121 std::string systemOsStatusPath;
122 std::string systemOsStatusService;
123 try
124 {
125 ipmi::DbusObjectInfo restrictionObj =
126 ipmi::getDbusObject(*bus, restrictionModeIntf);
127
128 restrictionModePath = restrictionObj.first;
129 restrictionModeService = restrictionObj.second;
130
131 ipmi::DbusObjectInfo postCompleteObj =
132 ipmi::getDbusObject(*bus, systemOsStatusIntf);
133
134 systemOsStatusPath = postCompleteObj.first;
135 systemOsStatusService = postCompleteObj.second;
136 }
137 catch (const std::exception&)
138 {
139 log<level::ERR>(
140 "Could not initialize provisioning mode, defaulting to restricted",
141 entry("VALUE=%d", static_cast<int>(restrictionMode)));
142 return;
143 }
144
145 bus->async_method_call(
146 [this](boost::system::error_code ec, ipmi::Value v) {
147 if (ec)
148 {
149 log<level::ERR>(
150 "Could not initialize provisioning mode, "
151 "defaulting to restricted",
152 entry("VALUE=%d", static_cast<int>(restrictionMode)));
153 return;
154 }
155 auto mode = std::get<std::string>(v);
156 restrictionMode = RestrictionMode::convertModesFromString(mode);
157 log<level::INFO>(
158 "Read restriction mode",
159 entry("VALUE=%d", static_cast<int>(restrictionMode)));
160 },
161 restrictionModeService, restrictionModePath,
162 "org.freedesktop.DBus.Properties", "Get", restrictionModeIntf,
163 "RestrictionMode");
164
165 bus->async_method_call(
166 [this](boost::system::error_code ec, const ipmi::Value& v) {
167 if (ec)
168 {
169 log<level::ERR>("Error in OperatingSystemState Get");
170 postCompleted = true;
171 return;
172 }
173 auto value = std::get<std::string>(v);
174 if (value == "Standby")
175 {
176 postCompleted = true;
177 }
178 else
179 {
180 postCompleted = false;
181 }
182 log<level::INFO>("Read POST complete value",
183 entry("VALUE=%d", postCompleted));
184 },
185 systemOsStatusService, systemOsStatusPath,
186 "org.freedesktop.DBus.Properties", "Get", systemOsStatusIntf,
187 "OperatingSystemState");
188}
189
190void WhitelistFilter::updateRestrictionMode(const std::string& value)
191{
192 restrictionMode = RestrictionMode::convertModesFromString(value);
193 log<level::INFO>("Updated restriction mode",
194 entry("VALUE=%d", static_cast<int>(restrictionMode)));
195}
196
197void WhitelistFilter::handleRestrictedModeChange(sdbusplus::message::message& m)
198{
199 std::string signal = m.get_member();
200 if (signal == "PropertiesChanged")
201 {
202 std::string intf;
203 std::vector<std::pair<std::string, ipmi::Value>> propertyList;
204 m.read(intf, propertyList);
205 for (const auto& property : propertyList)
206 {
207 if (property.first == "RestrictionMode")
208 {
209 updateRestrictionMode(std::get<std::string>(property.second));
210 }
211 }
212 }
213 else if (signal == "InterfacesAdded")
214 {
215 sdbusplus::message::object_path path;
216 DbusInterfaceMap restModeObj;
217 m.read(path, restModeObj);
218 auto intfItr = restModeObj.find(restrictionModeIntf);
219 if (intfItr == restModeObj.end())
220 {
221 return;
222 }
223 PropertyMap& propertyList = intfItr->second;
224 auto itr = propertyList.find("RestrictionMode");
225 if (itr == propertyList.end())
226 {
227 return;
228 }
229 updateRestrictionMode(std::get<std::string>(itr->second));
230 }
231}
232
233void WhitelistFilter::updatePostComplete(const std::string& value)
234{
235 if (value == "Standby")
236 {
237 postCompleted = true;
238 }
239 else
240 {
241 postCompleted = false;
242 }
243 log<level::INFO>(postCompleted ? "Updated to POST Complete"
244 : "Updated to !POST Complete");
245}
246
247void WhitelistFilter::handlePostCompleteChange(sdbusplus::message::message& m)
248{
249 std::string signal = m.get_member();
250 if (signal == "PropertiesChanged")
251 {
252 std::string intf;
253 std::vector<std::pair<std::string, ipmi::Value>> propertyList;
254 m.read(intf, propertyList);
255 for (const auto& property : propertyList)
256 {
257 if (property.first == "OperatingSystemState")
258 {
259 updatePostComplete(std::get<std::string>(property.second));
260 }
261 }
262 }
263 else if (signal == "InterfacesAdded")
264 {
265 sdbusplus::message::object_path path;
266 DbusInterfaceMap postCompleteObj;
267 m.read(path, postCompleteObj);
268 auto intfItr = postCompleteObj.find(systemOsStatusIntf);
269 if (intfItr == postCompleteObj.end())
270 {
271 return;
272 }
273 PropertyMap& propertyList = intfItr->second;
274 auto itr = propertyList.find("OperatingSystemState");
275 if (itr == propertyList.end())
276 {
277 return;
278 }
279 updatePostComplete(std::get<std::string>(itr->second));
280 }
281}
Arun P. Mohanana4cea1a2020-03-18 16:01:57 +0530282
283void WhitelistFilter::cacheCoreBiosDone()
284{
285 std::string coreBiosDonePath;
286 std::string coreBiosDoneService;
287 try
288 {
289 ipmi::DbusObjectInfo coreBiosDoneObj =
290 ipmi::getDbusObject(*bus, hostMiscIntf);
291
292 coreBiosDonePath = coreBiosDoneObj.first;
293 coreBiosDoneService = coreBiosDoneObj.second;
294 }
295 catch (const std::exception&)
296 {
297 log<level::ERR>("Could not initialize CoreBiosDone, "
298 "coreBIOSDone asserted as default");
299 return;
300 }
301
302 bus->async_method_call(
303 [this](boost::system::error_code ec, const ipmi::Value& v) {
304 if (ec)
305 {
306 log<level::ERR>(
307 "async call failed, coreBIOSDone asserted as default");
308 return;
309 }
310 coreBIOSDone = std::get<bool>(v);
311 log<level::INFO>("Read CoreBiosDone",
312 entry("VALUE=%d", static_cast<int>(coreBIOSDone)));
313 },
314 coreBiosDoneService, coreBiosDonePath,
315 "org.freedesktop.DBus.Properties", "Get", hostMiscIntf, "CoreBiosDone");
316}
317
318void WhitelistFilter::handleCoreBiosDoneChange(sdbusplus::message::message& msg)
319{
320 std::string signal = msg.get_member();
321 if (signal == "PropertiesChanged")
322 {
323 std::string intf;
324 std::vector<std::pair<std::string, ipmi::Value>> propertyList;
325 msg.read(intf, propertyList);
326 auto it =
327 std::find_if(propertyList.begin(), propertyList.end(),
328 [](const std::pair<std::string, ipmi::Value>& prop) {
329 return prop.first == "CoreBiosDone";
330 });
331
332 if (it != propertyList.end())
333 {
334 coreBIOSDone = std::get<bool>(it->second);
335 log<level::INFO>(coreBIOSDone ? "coreBIOSDone asserted"
336 : "coreBIOSDone not asserted");
337 }
338 }
339 else if (signal == "InterfacesAdded")
340 {
341 sdbusplus::message::object_path path;
342 DbusInterfaceMap eSpiresetObj;
343 msg.read(path, eSpiresetObj);
344 auto intfItr = eSpiresetObj.find(hostMiscIntf);
345 if (intfItr == eSpiresetObj.end())
346 {
347 return;
348 }
349 PropertyMap& propertyList = intfItr->second;
350 auto itr = propertyList.find("CoreBiosDone");
351 if (itr == propertyList.end())
352 {
353 return;
354 }
355 coreBIOSDone = std::get<bool>(itr->second);
356 log<level::INFO>(coreBIOSDone ? "coreBIOSDone asserted"
357 : "coreBIOSDone not asserted");
358 }
359}
360
Vernon Mauery0d0cd162020-01-31 10:04:10 -0800361void WhitelistFilter::postInit()
362{
363 // Wait for changes on Restricted mode
364 namespace rules = sdbusplus::bus::match::rules;
365 const std::string filterStrModeChange =
366 rules::type::signal() + rules::member("PropertiesChanged") +
367 rules::interface("org.freedesktop.DBus.Properties") +
368 rules::argN(0, restrictionModeIntf);
369
370 const std::string filterStrModeIntfAdd =
371 rules::interfacesAdded() +
372 rules::argNpath(
373 0, "/xyz/openbmc_project/control/security/restriction_mode");
374
375 const std::string filterStrPostComplete =
376 rules::type::signal() + rules::member("PropertiesChanged") +
377 rules::interface("org.freedesktop.DBus.Properties") +
378 rules::argN(0, systemOsStatusIntf);
379
380 const std::string filterStrPostIntfAdd =
381 rules::interfacesAdded() +
382 rules::argNpath(0, "/xyz/openbmc_project/state/os");
383
Arun P. Mohanana4cea1a2020-03-18 16:01:57 +0530384 const std::string filterStrPlatStateChange =
385 rules::type::signal() + rules::member("PropertiesChanged") +
386 rules::interface("org.freedesktop.DBus.Properties") +
387 rules::argN(0, hostMiscIntf);
388
389 const std::string filterStrPlatStateIntfAdd =
390 rules::interfacesAdded() +
391 rules::argNpath(0, "/xyz/openbmc_project/misc/platform_state");
392
Vernon Mauery0d0cd162020-01-31 10:04:10 -0800393 modeChangeMatch = std::make_unique<sdbusplus::bus::match::match>(
394 *bus, filterStrModeChange, [this](sdbusplus::message::message& m) {
395 handleRestrictedModeChange(m);
396 });
397 modeIntfAddedMatch = std::make_unique<sdbusplus::bus::match::match>(
398 *bus, filterStrModeIntfAdd, [this](sdbusplus::message::message& m) {
399 handleRestrictedModeChange(m);
400 });
401
402 postCompleteMatch = std::make_unique<sdbusplus::bus::match::match>(
403 *bus, filterStrPostComplete, [this](sdbusplus::message::message& m) {
404 handlePostCompleteChange(m);
405 });
406
407 postCompleteIntfAddedMatch = std::make_unique<sdbusplus::bus::match::match>(
408 *bus, filterStrPostIntfAdd, [this](sdbusplus::message::message& m) {
409 handlePostCompleteChange(m);
410 });
411
Arun P. Mohanana4cea1a2020-03-18 16:01:57 +0530412 platStateChangeMatch = std::make_unique<sdbusplus::bus::match::match>(
413 *bus, filterStrPlatStateChange, [this](sdbusplus::message::message& m) {
414 handleCoreBiosDoneChange(m);
415 });
416
417 platStateIntfAddedMatch = std::make_unique<sdbusplus::bus::match::match>(
418 *bus, filterStrPlatStateIntfAdd,
419 [this](sdbusplus::message::message& m) {
420 handleCoreBiosDoneChange(m);
421 });
422
Vernon Mauery0d0cd162020-01-31 10:04:10 -0800423 // Initialize restricted mode
424 cacheRestrictedAndPostCompleteMode();
Arun P. Mohanana4cea1a2020-03-18 16:01:57 +0530425 // Initialize CoreBiosDone
426 cacheCoreBiosDone();
Vernon Mauery0d0cd162020-01-31 10:04:10 -0800427}
428
429ipmi::Cc WhitelistFilter::filterMessage(ipmi::message::Request::ptr request)
430{
431 auto channelMask = static_cast<unsigned short>(1 << request->ctx->channel);
432 bool whitelisted = std::binary_search(
433 whitelist.cbegin(), whitelist.cend(),
434 std::make_tuple(request->ctx->netFn, request->ctx->cmd, channelMask),
435 [](const netfncmd_tuple& first, const netfncmd_tuple& value) {
436 return (std::get<2>(first) & std::get<2>(value))
437 ? first < std::make_tuple(std::get<0>(value),
438 std::get<1>(value),
439 std::get<2>(first))
440 : first < value;
441 });
442
443 // no special handling for non-system-interface channels
arun-pm849c3192020-02-12 18:10:32 +0530444 if (!(request->ctx->channel == ipmi::channelSystemIface ||
445 request->ctx->channel == channelSMM))
Vernon Mauery0d0cd162020-01-31 10:04:10 -0800446 {
447 if (!whitelisted)
448 {
449 log<level::INFO>("Channel/NetFn/Cmd not whitelisted",
450 entry("CHANNEL=0x%X", request->ctx->channel),
451 entry("NETFN=0x%X", int(request->ctx->netFn)),
452 entry("CMD=0x%X", int(request->ctx->cmd)));
453 return ipmi::ccCommandNotAvailable;
454 }
455 return ipmi::ccSuccess;
456 }
457
458 // for system interface, filtering is done as follows:
459 // Allow All: preboot ? ccSuccess : ccSuccess
460 // Restricted: preboot ? ccSuccess :
461 // ( whitelist ? ccSuccess : // ccCommandNotAvailable )
462 // Deny All: preboot ? ccSuccess : ccCommandNotAvailable
463
Arun P. Mohanana4cea1a2020-03-18 16:01:57 +0530464 if (!(postCompleted || coreBIOSDone))
Vernon Mauery0d0cd162020-01-31 10:04:10 -0800465 {
Arun P. Mohanana4cea1a2020-03-18 16:01:57 +0530466 // Allow all commands, till POST or CoreBiosDone is completed
Vernon Mauery0d0cd162020-01-31 10:04:10 -0800467 return ipmi::ccSuccess;
468 }
469
470 switch (restrictionMode)
471 {
472 case RestrictionMode::Modes::None:
473 case restrictionModeAllowAll:
474 {
475 // Allow All
476 return ipmi::ccSuccess;
477 break;
478 }
479 case restrictionModeRestricted:
480 {
481 // Restricted - follow whitelist
482 break;
483 }
484 case restrictionModeDenyAll:
485 {
486 // Deny All
487 whitelisted = false;
488 break;
489 }
490 default: // for whitelist and blacklist
491 return ipmi::ccCommandNotAvailable;
492 }
493
494 if (!whitelisted)
495 {
496 log<level::INFO>("Channel/NetFn/Cmd not whitelisted",
497 entry("CHANNEL=0x%X", request->ctx->channel),
498 entry("NETFN=0x%X", int(request->ctx->netFn)),
499 entry("CMD=0x%X", int(request->ctx->cmd)));
500 return ipmi::ccCommandNotAvailable;
501 }
502 return ipmi::ccSuccess;
503} // namespace
504
505// instantiate the WhitelistFilter when this shared object is loaded
506WhitelistFilter whitelistFilter;
507
508} // namespace
509
510} // namespace ipmi