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