blob: 0b78578b7fd54b388a5790443694e63762829f8c [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";
Arun P. Mohanan31c45f62021-10-18 21:52:39 +053076 static constexpr const char* restrictionModePath =
77 "/xyz/openbmc_project/control/security/restriction_mode";
78 static constexpr const char* systemOsStatusPath =
79 "/xyz/openbmc_project/state/os";
Vernon Mauery0d0cd162020-01-31 10:04:10 -080080};
81
arun-pm849c3192020-02-12 18:10:32 +053082static inline uint8_t getSMMChannel()
83{
84 ipmi::ChannelInfo chInfo;
85
86 for (int channel = 0; channel < ipmi::maxIpmiChannels; channel++)
87 {
88 if (ipmi::getChannelInfo(channel, chInfo) != ipmi::ccSuccess)
89 {
90 continue;
91 }
92
93 if (static_cast<ipmi::EChannelMediumType>(chInfo.mediumType) ==
94 ipmi::EChannelMediumType::systemInterface &&
95 channel != ipmi::channelSystemIface)
96 {
97 log<level::INFO>("SMM channel number",
98 entry("CHANNEL=%d", channel));
99 return channel;
100 }
101 }
102 log<level::ERR>("Unable to find SMM Channel Info");
103 return -1;
104}
105
Vernon Mauery0d0cd162020-01-31 10:04:10 -0800106WhitelistFilter::WhitelistFilter()
107{
108 bus = getSdBus();
109
110 log<level::INFO>("Loading whitelist filter");
111
112 ipmi::registerFilter(ipmi::prioOpenBmcBase,
113 [this](ipmi::message::Request::ptr request) {
114 return filterMessage(request);
115 });
116
arun-pm849c3192020-02-12 18:10:32 +0530117 channelSMM = getSMMChannel();
Vernon Mauery0d0cd162020-01-31 10:04:10 -0800118 // wait until io->run is going to fetch RestrictionMode
119 post_work([this]() { postInit(); });
120}
121
122void WhitelistFilter::cacheRestrictedAndPostCompleteMode()
123{
Vernon Mauery0d0cd162020-01-31 10:04:10 -0800124 try
125 {
Arun P. Mohanan31c45f62021-10-18 21:52:39 +0530126 auto service =
127 ipmi::getService(*bus, restrictionModeIntf, restrictionModePath);
128 ipmi::Value v =
129 ipmi::getDbusProperty(*bus, service, restrictionModePath,
130 restrictionModeIntf, "RestrictionMode");
131 auto& mode = std::get<std::string>(v);
132 restrictionMode = RestrictionMode::convertModesFromString(mode);
133 log<level::INFO>("Read restriction mode",
134 entry("VALUE=%d", static_cast<int>(restrictionMode)));
Vernon Mauery0d0cd162020-01-31 10:04:10 -0800135 }
136 catch (const std::exception&)
137 {
Arun P. Mohanan31c45f62021-10-18 21:52:39 +0530138 log<level::ERR>("Could not initialize provisioning mode, "
139 "defaulting to restricted",
140 entry("VALUE=%d", static_cast<int>(restrictionMode)));
Vernon Mauery0d0cd162020-01-31 10:04:10 -0800141 }
142
Arun P. Mohanan31c45f62021-10-18 21:52:39 +0530143 try
144 {
145 auto service =
146 ipmi::getService(*bus, systemOsStatusIntf, systemOsStatusPath);
147 ipmi::Value v =
148 ipmi::getDbusProperty(*bus, service, systemOsStatusPath,
149 systemOsStatusIntf, "OperatingSystemState");
150 auto& value = std::get<std::string>(v);
151 postCompleted = (value == "Standby");
152 log<level::INFO>("Read POST complete value",
153 entry("VALUE=%d", postCompleted));
154 }
155 catch (const std::exception&)
156 {
157 log<level::ERR>("Error in OperatingSystemState Get");
158 postCompleted = true;
159 }
Vernon Mauery0d0cd162020-01-31 10:04:10 -0800160}
161
162void WhitelistFilter::updateRestrictionMode(const std::string& value)
163{
164 restrictionMode = RestrictionMode::convertModesFromString(value);
165 log<level::INFO>("Updated restriction mode",
166 entry("VALUE=%d", static_cast<int>(restrictionMode)));
167}
168
169void WhitelistFilter::handleRestrictedModeChange(sdbusplus::message::message& m)
170{
171 std::string signal = m.get_member();
172 if (signal == "PropertiesChanged")
173 {
174 std::string intf;
175 std::vector<std::pair<std::string, ipmi::Value>> propertyList;
176 m.read(intf, propertyList);
177 for (const auto& property : propertyList)
178 {
179 if (property.first == "RestrictionMode")
180 {
181 updateRestrictionMode(std::get<std::string>(property.second));
182 }
183 }
184 }
185 else if (signal == "InterfacesAdded")
186 {
187 sdbusplus::message::object_path path;
188 DbusInterfaceMap restModeObj;
189 m.read(path, restModeObj);
190 auto intfItr = restModeObj.find(restrictionModeIntf);
191 if (intfItr == restModeObj.end())
192 {
193 return;
194 }
195 PropertyMap& propertyList = intfItr->second;
196 auto itr = propertyList.find("RestrictionMode");
197 if (itr == propertyList.end())
198 {
199 return;
200 }
201 updateRestrictionMode(std::get<std::string>(itr->second));
202 }
203}
204
205void WhitelistFilter::updatePostComplete(const std::string& value)
206{
207 if (value == "Standby")
208 {
209 postCompleted = true;
210 }
211 else
212 {
213 postCompleted = false;
214 }
215 log<level::INFO>(postCompleted ? "Updated to POST Complete"
216 : "Updated to !POST Complete");
217}
218
219void WhitelistFilter::handlePostCompleteChange(sdbusplus::message::message& m)
220{
221 std::string signal = m.get_member();
222 if (signal == "PropertiesChanged")
223 {
224 std::string intf;
225 std::vector<std::pair<std::string, ipmi::Value>> propertyList;
226 m.read(intf, propertyList);
227 for (const auto& property : propertyList)
228 {
229 if (property.first == "OperatingSystemState")
230 {
231 updatePostComplete(std::get<std::string>(property.second));
232 }
233 }
234 }
235 else if (signal == "InterfacesAdded")
236 {
237 sdbusplus::message::object_path path;
238 DbusInterfaceMap postCompleteObj;
239 m.read(path, postCompleteObj);
240 auto intfItr = postCompleteObj.find(systemOsStatusIntf);
241 if (intfItr == postCompleteObj.end())
242 {
243 return;
244 }
245 PropertyMap& propertyList = intfItr->second;
246 auto itr = propertyList.find("OperatingSystemState");
247 if (itr == propertyList.end())
248 {
249 return;
250 }
251 updatePostComplete(std::get<std::string>(itr->second));
252 }
253}
Arun P. Mohanana4cea1a2020-03-18 16:01:57 +0530254
255void WhitelistFilter::cacheCoreBiosDone()
256{
257 std::string coreBiosDonePath;
258 std::string coreBiosDoneService;
259 try
260 {
261 ipmi::DbusObjectInfo coreBiosDoneObj =
262 ipmi::getDbusObject(*bus, hostMiscIntf);
263
264 coreBiosDonePath = coreBiosDoneObj.first;
265 coreBiosDoneService = coreBiosDoneObj.second;
266 }
267 catch (const std::exception&)
268 {
269 log<level::ERR>("Could not initialize CoreBiosDone, "
270 "coreBIOSDone asserted as default");
271 return;
272 }
273
274 bus->async_method_call(
275 [this](boost::system::error_code ec, const ipmi::Value& v) {
276 if (ec)
277 {
278 log<level::ERR>(
279 "async call failed, coreBIOSDone asserted as default");
280 return;
281 }
282 coreBIOSDone = std::get<bool>(v);
283 log<level::INFO>("Read CoreBiosDone",
284 entry("VALUE=%d", static_cast<int>(coreBIOSDone)));
285 },
286 coreBiosDoneService, coreBiosDonePath,
287 "org.freedesktop.DBus.Properties", "Get", hostMiscIntf, "CoreBiosDone");
288}
289
290void WhitelistFilter::handleCoreBiosDoneChange(sdbusplus::message::message& msg)
291{
292 std::string signal = msg.get_member();
293 if (signal == "PropertiesChanged")
294 {
295 std::string intf;
296 std::vector<std::pair<std::string, ipmi::Value>> propertyList;
297 msg.read(intf, propertyList);
298 auto it =
299 std::find_if(propertyList.begin(), propertyList.end(),
300 [](const std::pair<std::string, ipmi::Value>& prop) {
301 return prop.first == "CoreBiosDone";
302 });
303
304 if (it != propertyList.end())
305 {
306 coreBIOSDone = std::get<bool>(it->second);
307 log<level::INFO>(coreBIOSDone ? "coreBIOSDone asserted"
308 : "coreBIOSDone not asserted");
309 }
310 }
311 else if (signal == "InterfacesAdded")
312 {
313 sdbusplus::message::object_path path;
314 DbusInterfaceMap eSpiresetObj;
315 msg.read(path, eSpiresetObj);
316 auto intfItr = eSpiresetObj.find(hostMiscIntf);
317 if (intfItr == eSpiresetObj.end())
318 {
319 return;
320 }
321 PropertyMap& propertyList = intfItr->second;
322 auto itr = propertyList.find("CoreBiosDone");
323 if (itr == propertyList.end())
324 {
325 return;
326 }
327 coreBIOSDone = std::get<bool>(itr->second);
328 log<level::INFO>(coreBIOSDone ? "coreBIOSDone asserted"
329 : "coreBIOSDone not asserted");
330 }
331}
332
Vernon Mauery0d0cd162020-01-31 10:04:10 -0800333void WhitelistFilter::postInit()
334{
335 // Wait for changes on Restricted mode
336 namespace rules = sdbusplus::bus::match::rules;
337 const std::string filterStrModeChange =
338 rules::type::signal() + rules::member("PropertiesChanged") +
339 rules::interface("org.freedesktop.DBus.Properties") +
340 rules::argN(0, restrictionModeIntf);
341
342 const std::string filterStrModeIntfAdd =
343 rules::interfacesAdded() +
344 rules::argNpath(
345 0, "/xyz/openbmc_project/control/security/restriction_mode");
346
347 const std::string filterStrPostComplete =
348 rules::type::signal() + rules::member("PropertiesChanged") +
349 rules::interface("org.freedesktop.DBus.Properties") +
350 rules::argN(0, systemOsStatusIntf);
351
352 const std::string filterStrPostIntfAdd =
353 rules::interfacesAdded() +
354 rules::argNpath(0, "/xyz/openbmc_project/state/os");
355
Arun P. Mohanana4cea1a2020-03-18 16:01:57 +0530356 const std::string filterStrPlatStateChange =
357 rules::type::signal() + rules::member("PropertiesChanged") +
358 rules::interface("org.freedesktop.DBus.Properties") +
359 rules::argN(0, hostMiscIntf);
360
361 const std::string filterStrPlatStateIntfAdd =
362 rules::interfacesAdded() +
363 rules::argNpath(0, "/xyz/openbmc_project/misc/platform_state");
364
Vernon Mauery0d0cd162020-01-31 10:04:10 -0800365 modeChangeMatch = std::make_unique<sdbusplus::bus::match::match>(
366 *bus, filterStrModeChange, [this](sdbusplus::message::message& m) {
367 handleRestrictedModeChange(m);
368 });
369 modeIntfAddedMatch = std::make_unique<sdbusplus::bus::match::match>(
370 *bus, filterStrModeIntfAdd, [this](sdbusplus::message::message& m) {
371 handleRestrictedModeChange(m);
372 });
373
374 postCompleteMatch = std::make_unique<sdbusplus::bus::match::match>(
375 *bus, filterStrPostComplete, [this](sdbusplus::message::message& m) {
376 handlePostCompleteChange(m);
377 });
378
379 postCompleteIntfAddedMatch = std::make_unique<sdbusplus::bus::match::match>(
380 *bus, filterStrPostIntfAdd, [this](sdbusplus::message::message& m) {
381 handlePostCompleteChange(m);
382 });
383
Arun P. Mohanana4cea1a2020-03-18 16:01:57 +0530384 platStateChangeMatch = std::make_unique<sdbusplus::bus::match::match>(
385 *bus, filterStrPlatStateChange, [this](sdbusplus::message::message& m) {
386 handleCoreBiosDoneChange(m);
387 });
388
389 platStateIntfAddedMatch = std::make_unique<sdbusplus::bus::match::match>(
390 *bus, filterStrPlatStateIntfAdd,
391 [this](sdbusplus::message::message& m) {
392 handleCoreBiosDoneChange(m);
393 });
394
Vernon Mauery0d0cd162020-01-31 10:04:10 -0800395 // Initialize restricted mode
396 cacheRestrictedAndPostCompleteMode();
Arun P. Mohanana4cea1a2020-03-18 16:01:57 +0530397 // Initialize CoreBiosDone
398 cacheCoreBiosDone();
Vernon Mauery0d0cd162020-01-31 10:04:10 -0800399}
400
401ipmi::Cc WhitelistFilter::filterMessage(ipmi::message::Request::ptr request)
402{
403 auto channelMask = static_cast<unsigned short>(1 << request->ctx->channel);
404 bool whitelisted = std::binary_search(
405 whitelist.cbegin(), whitelist.cend(),
406 std::make_tuple(request->ctx->netFn, request->ctx->cmd, channelMask),
407 [](const netfncmd_tuple& first, const netfncmd_tuple& value) {
408 return (std::get<2>(first) & std::get<2>(value))
409 ? first < std::make_tuple(std::get<0>(value),
410 std::get<1>(value),
411 std::get<2>(first))
412 : first < value;
413 });
414
415 // no special handling for non-system-interface channels
arun-pm849c3192020-02-12 18:10:32 +0530416 if (!(request->ctx->channel == ipmi::channelSystemIface ||
417 request->ctx->channel == channelSMM))
Vernon Mauery0d0cd162020-01-31 10:04:10 -0800418 {
419 if (!whitelisted)
420 {
421 log<level::INFO>("Channel/NetFn/Cmd not whitelisted",
422 entry("CHANNEL=0x%X", request->ctx->channel),
423 entry("NETFN=0x%X", int(request->ctx->netFn)),
424 entry("CMD=0x%X", int(request->ctx->cmd)));
Snehalatha V65b66ad2020-07-09 14:58:39 +0000425 return ipmi::ccInsufficientPrivilege;
Vernon Mauery0d0cd162020-01-31 10:04:10 -0800426 }
427 return ipmi::ccSuccess;
428 }
429
430 // for system interface, filtering is done as follows:
431 // Allow All: preboot ? ccSuccess : ccSuccess
432 // Restricted: preboot ? ccSuccess :
Snehalatha V65b66ad2020-07-09 14:58:39 +0000433 // ( whitelist ? ccSuccess : ccInsufficientPrivilege )
434 // Deny All: preboot ? ccSuccess : ccInsufficientPrivilege
Vernon Mauery0d0cd162020-01-31 10:04:10 -0800435
Arun P. Mohanana4cea1a2020-03-18 16:01:57 +0530436 if (!(postCompleted || coreBIOSDone))
Vernon Mauery0d0cd162020-01-31 10:04:10 -0800437 {
Arun P. Mohanana4cea1a2020-03-18 16:01:57 +0530438 // Allow all commands, till POST or CoreBiosDone is completed
Vernon Mauery0d0cd162020-01-31 10:04:10 -0800439 return ipmi::ccSuccess;
440 }
441
442 switch (restrictionMode)
443 {
444 case RestrictionMode::Modes::None:
445 case restrictionModeAllowAll:
446 {
447 // Allow All
448 return ipmi::ccSuccess;
449 break;
450 }
451 case restrictionModeRestricted:
452 {
453 // Restricted - follow whitelist
454 break;
455 }
456 case restrictionModeDenyAll:
457 {
458 // Deny All
459 whitelisted = false;
460 break;
461 }
462 default: // for whitelist and blacklist
Snehalatha V65b66ad2020-07-09 14:58:39 +0000463 return ipmi::ccInsufficientPrivilege;
Vernon Mauery0d0cd162020-01-31 10:04:10 -0800464 }
465
466 if (!whitelisted)
467 {
468 log<level::INFO>("Channel/NetFn/Cmd not whitelisted",
469 entry("CHANNEL=0x%X", request->ctx->channel),
470 entry("NETFN=0x%X", int(request->ctx->netFn)),
471 entry("CMD=0x%X", int(request->ctx->cmd)));
Snehalatha V65b66ad2020-07-09 14:58:39 +0000472 return ipmi::ccInsufficientPrivilege;
Vernon Mauery0d0cd162020-01-31 10:04:10 -0800473 }
474 return ipmi::ccSuccess;
475} // namespace
476
477// instantiate the WhitelistFilter when this shared object is loaded
478WhitelistFilter whitelistFilter;
479
480} // namespace
481
482} // namespace ipmi