1 #include <ipmiallowlist.hpp>
2 #include <ipmid/api.hpp>
3 #include <ipmid/utils.hpp>
4 #include <phosphor-logging/elog-errors.hpp>
5 #include <phosphor-logging/lg2.hpp>
6 #include <settings.hpp>
7 #include <xyz/openbmc_project/Common/error.hpp>
8 #include <xyz/openbmc_project/Control/Security/RestrictionMode/server.hpp>
9 
10 #include <algorithm>
11 #include <array>
12 
13 using namespace phosphor::logging;
14 using namespace sdbusplus::error::xyz::openbmc_project::common;
15 
16 namespace ipmi
17 {
18 
19 // put the filter provider in an unnamed namespace
20 namespace
21 {
22 
23 /** @class AllowlistFilter
24  *
25  * Class that implements an IPMI message filter based
26  * on incoming interface and a restriction mode setting
27  */
28 class AllowlistFilter
29 {
30   public:
31     AllowlistFilter();
32     ~AllowlistFilter() = default;
33     AllowlistFilter(const AllowlistFilter&) = delete;
34     AllowlistFilter(AllowlistFilter&&) = delete;
35     AllowlistFilter& operator=(const AllowlistFilter&) = delete;
36     AllowlistFilter& operator=(AllowlistFilter&&) = delete;
37 
38   private:
39     void postInit();
40     void cacheRestrictedMode(const std::vector<std::string>& devices);
41     void handleRestrictedModeChange(
42         sdbusplus::message_t& m,
43         const std::map<std::string, size_t>& deviceList);
44     ipmi::Cc filterMessage(ipmi::message::Request::ptr request);
45 
46     std::vector<bool> restrictedMode;
47     std::shared_ptr<sdbusplus::asio::connection> bus;
48     std::unique_ptr<settings::Objects> objects;
49     std::unique_ptr<sdbusplus::bus::match_t> modeChangeMatch;
50 
51     static constexpr const char restrictionModeIntf[] =
52         "xyz.openbmc_project.Control.Security.RestrictionMode";
53 };
54 
AllowlistFilter()55 AllowlistFilter::AllowlistFilter()
56 {
57     bus = getSdBus();
58 
59     lg2::info("Loading allowlist filter");
60     ipmi::registerFilter(ipmi::prioOpenBmcBase,
61                          [this](ipmi::message::Request::ptr request) {
62                              return filterMessage(request);
63                          });
64 
65     // wait until io->run is going to fetch RestrictionMode
66     post_work([this]() { postInit(); });
67 }
68 
69 /** @brief Get RestrictionMode of the devices which has RestrictionMode support
70  * enabled
71  *  @param[in] devices - vector of devices object path
72  *  @returns void.
73  */
74 
cacheRestrictedMode(const std::vector<std::string> & devices)75 void AllowlistFilter::cacheRestrictedMode(
76     const std::vector<std::string>& devices)
77 {
78     using namespace sdbusplus::server::xyz::openbmc_project::control::security;
79     std::string restrictionModeSetting;
80     std::string restrictionModeService;
81 
82     for (auto& dev : devices)
83     {
84         try
85         {
86             restrictionModeSetting = dev;
87             restrictionModeService =
88                 objects->service(restrictionModeSetting, restrictionModeIntf);
89         }
90         catch (const std::out_of_range& e)
91         {
92             lg2::error(
93                 "Could not look up restriction mode interface from cache");
94             return;
95         }
96 
97         std::string mode;
98         try
99         {
100             auto propValue = ipmi::getDbusProperty(
101                 *bus, restrictionModeService, restrictionModeSetting,
102                 restrictionModeIntf, "RestrictionMode");
103             mode = std::get<std::string>(propValue);
104         }
105         catch (const std::exception& e)
106         {
107             lg2::error("Error in RestrictionMode Get");
108             // Fail-safe to true.
109             size_t index = std::distance(&*std::begin(devices), &dev);
110             restrictedMode[index] = true;
111         }
112 
113         auto restrictionMode = RestrictionMode::convertModesFromString(mode);
114 
115         bool restrictMode =
116             (restrictionMode == RestrictionMode::Modes::Allowlist);
117         restrictedMode.emplace_back(restrictMode);
118 
119         lg2::info("Set restrictedMode = {RESTRICTED_MODE}", "RESTRICTED_MODE",
120                   restrictMode);
121     }
122 }
123 
124 /** @brief Update RestrictionMode if any changes in RestrictionMode
125  *  @param[in] m - sdbusplus message. Using this to get Updated Mode dbus path
126  *  @param[in] deviceList - map to store devices path and their index
127  *  @returns void.
128  */
129 
handleRestrictedModeChange(sdbusplus::message_t & m,const std::map<std::string,size_t> & deviceList)130 void AllowlistFilter::handleRestrictedModeChange(
131     sdbusplus::message_t& m, const std::map<std::string, size_t>& deviceList)
132 {
133     using namespace sdbusplus::server::xyz::openbmc_project::control::security;
134     std::string intf;
135     std::vector<std::pair<std::string, ipmi::Value>> propertyList;
136     m.read(intf, propertyList);
137 
138     std::string path = m.get_path();
139     size_t hostId = 0;
140     auto it = deviceList.find(path);
141 
142     if (it == deviceList.end())
143     {
144         lg2::error("Key not found in deviceList ");
145     }
146     else
147     {
148         hostId = it->second;
149     }
150 
151     for (const auto& property : propertyList)
152     {
153         if (property.first == "RestrictionMode")
154         {
155             RestrictionMode::Modes restrictionMode =
156                 RestrictionMode::convertModesFromString(
157                     std::get<std::string>(property.second));
158             bool restrictMode =
159                 (restrictionMode == RestrictionMode::Modes::Allowlist);
160             restrictedMode[hostId] = restrictMode;
161 
162             lg2::info("Updated restrictedMode = {RESTRICTED_MODE}",
163                       "RESTRICTED_MODE", restrictMode);
164         }
165     }
166 }
167 
168 /** @brief Get and Update RestrictionModes of supported devices
169  *  @param[in] void
170  *  @returns void.
171  */
172 
postInit()173 void AllowlistFilter::postInit()
174 {
175     objects = std::make_unique<settings::Objects>(
176         *bus, std::vector<settings::Interface>({restrictionModeIntf}));
177     if (!objects)
178     {
179         lg2::error(
180             "Failed to create settings object; defaulting to restricted mode");
181         return;
182     }
183 
184     std::vector<std::string> devices;
185     try
186     {
187         devices = objects->map.at(restrictionModeIntf);
188     }
189     catch (const std::out_of_range& e)
190     {
191         lg2::error("Could not look up restriction mode interface from cache");
192         return;
193     }
194 
195     // Initialize restricted mode
196     cacheRestrictedMode(devices);
197     // Wait for changes on Restricted mode
198     std::map<std::string, size_t> deviceList;
199 
200     for (size_t index = 0; index < devices.size(); index++)
201     {
202         deviceList.emplace(devices[index], index);
203     }
204 
205     std::string filterStr;
206     std::string devicesDbusPath{"/xyz/openbmc_project/control"};
207 
208     filterStr = sdbusplus::bus::match::rules::propertiesChangedNamespace(
209         devicesDbusPath, restrictionModeIntf);
210 
211     modeChangeMatch = std::make_unique<sdbusplus::bus::match_t>(
212         *bus, filterStr, [this, deviceList](sdbusplus::message_t& m) {
213             handleRestrictedModeChange(m, deviceList);
214         });
215 }
216 
217 /** @brief Filter IPMI messages with RestrictedMode
218  *  @param[in] request - IPMI messahe request
219  *  @returns IPMI completion code success or error.
220  */
221 
filterMessage(ipmi::message::Request::ptr request)222 ipmi::Cc AllowlistFilter::filterMessage(ipmi::message::Request::ptr request)
223 {
224     /* Getting hostIdx for all IPMI devices like hosts, debugcard and other
225    devices from ipmi::message::Request and call postInit() to get the
226    restriction mode for all the IPMI commands */
227 
228     size_t hostIdx = request->ctx->hostIdx;
229 
230     if (request->ctx->channel == ipmi::channelSystemIface &&
231         restrictedMode[hostIdx])
232     {
233         if (!std::binary_search(
234                 allowlist.cbegin(), allowlist.cend(),
235                 std::make_pair(request->ctx->netFn, request->ctx->cmd)))
236         {
237             lg2::error("Net function not allowlisted, "
238                        "NetFn: {NETFN}, Cmd: {CMD}",
239                        "NETFN", lg2::hex, request->ctx->netFn, "CMD", lg2::hex,
240                        request->ctx->cmd);
241 
242             return ipmi::ccInsufficientPrivilege;
243         }
244     }
245     return ipmi::ccSuccess;
246 }
247 
248 // instantiate the AllowlistFilter when this shared object is loaded
249 AllowlistFilter allowlistFilter;
250 
251 } // namespace
252 
253 } // namespace ipmi
254