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 = 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 
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