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 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 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 119 void AllowlistFilter::cacheRestrictedAndPostCompleteMode() 120 { 121 try 122 { 123 auto service = ipmi::getService(*bus, restrictionModeIntf, 124 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 = ipmi::getService(*bus, systemOsStatusIntf, 143 systemOsStatusPath); 144 ipmi::Value v = ipmi::getDbusProperty(*bus, service, systemOsStatusPath, 145 systemOsStatusIntf, 146 "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 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 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 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 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 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 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 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 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