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