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