1# Commonly recurring errors in bmcweb 2 3What follows is a list of common errors that new users to bmcweb tend to make 4when operating within its bounds for the first time. If this is your first time 5developing in bmcweb, the maintainers highly recommend reading and understanding 6_all_ of common traps before continuing with any development. Every single one 7of the examples below compile without warnings, but are incorrect in 8not-always-obvious ways, or impose a pattern that tends to cause hard to find 9bugs, or bugs that appear later. Every one has been submitted to code review 10multiple times. 11 12## 1. Directly dereferencing a pointer without checking for validity first 13 14```cpp 15int myBadMethod(const nlohmann::json& j){ 16 const int* myPtr = j.get_if<int>(); 17 return *myPtr; 18} 19``` 20 21This pointer is not guaranteed to be filled, and could be a null dereference. 22 23## 2. String views aren't null terminated 24 25```cpp 26int getIntFromString(std::string_view s){ 27 return std::atoi(s.data()); 28} 29``` 30 31This will give the right answer much of the time, but has the possibility to 32fail when `string_view` is not null terminated. Use `from_chars` instead, which 33takes both a pointer and a length 34 35## 3. Not handling input errors 36 37```cpp 38int getIntFromString(const std::string& s){ 39 return std::atoi(s.c_str()); 40} 41``` 42 43In the case where the string is not representable as an int, this will trigger 44undefined behavior at system level. Code needs to check for validity of the 45string, ideally with something like `from_chars`, and return the appropriate 46error code. 47 48## 4. Walking off the end of a string 49 50```cpp 51std::string getFilenameFromPath(const std::string& path){ 52 size_t index = path.find("/"); 53 if (index != std::string::npos){ 54 // If the string ends with "/", this will walk off the end of the string. 55 return path.substr(pos + 1); 56 } 57 return ""; 58} 59``` 60 61## 5. Using methods that throw (or not handling bad inputs) 62 63```cpp 64int myBadMethod(nlohmann::json& j){ 65 return j.get<int>(); 66} 67``` 68 69This method throws, and bad inputs will not be handled 70 71Commonly used methods that fall into this pattern: 72 73- std::variant::get 74- std::vector::at 75- std::map::at 76- std::set::at 77- std::\<generic container type\>::at 78- nlohmann::json::operator!= 79- nlohmann::json::operator+= 80- nlohmann::json::at 81- nlohmann::json::get 82- nlohmann::json::get_ref 83- nlohmann::json::get_to 84- nlohmann::json::items 85- nlohmann::json::operator\<\< 86- nlohmann::json::operator\>\> 87- std::filesystem::create_directory 88- std::filesystem::rename 89- std::filesystem::file_size 90- std::stoi 91- std::stol 92- std::stoll 93 94### Special note: JSON 95 96`nlohmann::json::parse` by default 97[throws](https://json.nlohmann.me/api/basic_json/parse/) on failure, but also 98accepts an optional argument that causes it to not throw: set the 3rd argument 99to `false`. 100 101`nlohmann::json::dump` by default 102[throws](https://json.nlohmann.me/api/basic_json/dump/) on failure, but also 103accepts an optional argument that causes it to not throw: set the 4th argument 104to `replace`. Although `ignore` preserves content 1:1, `replace` is preferred 105from a security point of view. 106 107### Special note: Boost 108 109there is a whole class of boost asio functions that provide both a method that 110throws on failure, and a method that accepts and returns an error code. This is 111not a complete list, but users should verify in the boost docs when calling into 112asio methods, and prefer the one that returns an error code instead of throwing. 113 114- boost::asio::ip::tcp::acceptor::bind(); 115- boost::asio::ip::tcp::acceptor::cancel(); 116- boost::asio::ip::tcp::acceptor::close(); 117- boost::asio::ip::tcp::acceptor::listen(); 118- boost::asio::ip::address::make_address(); 119 120## 6. Blocking functions 121 122bmcweb uses a single reactor for all operations. Blocking that reactor for any 123amount of time causes all other operations to stop. The common blocking 124functions that tend to be called incorrectly are: 125 126- sleep() 127- boost::asio::ip::tcp::socket::read() 128- boost::asio::ip::tcp::socket::read_some() 129- boost::asio::ip::tcp::socket::write() 130- boost::asio::ip::tcp::socket::write_some() 131- boost::asio::ip::tcp::socket::connect() 132- boost::asio::ip::tcp::socket::send() 133- boost::asio::ip::tcp::socket::wait() 134- boost::asio::steady_timer::wait() 135 136Note: an exception is made for filesystem/disk IO read and write. This is mostly 137due to not having great abstractions for it that mate well with the async 138system, the fact that most filesystem accesses are into tmpfs (and therefore 139should be "fast" most of the time) and in general how little the filesystem is 140used in practice. 141 142## 7. Lack of locking between subsequent calls 143 144While global data structures are discouraged, they are sometimes required to 145store temporary state for operations that require it. Given the single threaded 146nature of bmcweb, they are not required to be explicitly threadsafe, but they 147must be always left in a valid state, and checked for other uses before 148occupying. 149 150```cpp 151std::optional<std::string> currentOperation; 152void firstCallbackInFlow(){ 153 currentOperation = "Foo"; 154} 155void secondCallbackInFlow(){ 156 currentOperation.reset(); 157} 158``` 159 160In the above case, the first callback needs a check to ensure that 161currentOperation is not already being used. 162 163## 8. Wildcard reference captures in lambdas 164 165```cpp 166std::string x; auto mylambda = [&](){ 167 x = "foo"; 168} 169do_async_read(mylambda) 170``` 171 172Numerous times, lifetime issues of const references have been injected into 173async bmcweb code. While capturing by reference can be useful, given how 174difficult these types of bugs are to triage, bmcweb explicitly requires that all 175code captures variables by name explicitly, and calls out each variable being 176captured by value or by reference. The above prototypes would change to 177`[&x]()...` Which makes clear that x is captured, and its lifetime needs 178tracked. 179 180## 9. URLs should end in "/" 181 182```cpp 183BMCWEB("/foo/bar"); 184``` 185 186Unless you explicitly have a reason not to (as there is one known exception 187where the behavior must differ) all URL handlers should end in "/". The bmcweb 188route handler will detect routes ending in slash and generate routes for both 189the route ending in slash and the one without. This allows both URLs to be used 190by users. While many specifications do not require this, it resolves a whole 191class of bug that we've seen in the past. 192 193## 10. URLs constructed in aggregate 194 195```cpp 196std::string routeStart = "/redfish/v1"; 197 198BMCWEB_ROUTE(routestart + "/SessionService/") 199``` 200 201Very commonly, bmcweb maintainers and contributors alike have to do audits of 202all routes that are available, to verify things like security and documentation 203accuracy. While these processes are largely manual, they can mostly be conducted 204by a simple grep statement to search for urls in question. Doing the above makes 205the route handlers no longer greppable, and complicates bmcweb patchsets as a 206whole. 207 208## 11. Not responding to 404 209 210```cpp 211BMCWEB_ROUTE("/myendpoint/<str>", 212 [](Request& req, Response& res, const std::string& id){ 213 crow::connections::systemBus->async_method_call( 214 [asyncResp](const boost::system::error_code& ec, 215 const std::string& myProperty) { 216 if (ec) 217 { 218 messages::internalError(asyncResp->res); 219 return; 220 } 221 ... handle code 222 }, 223 "xyz.openbmc_project.Logging", 224 "/xyz/openbmc_project/mypath/" + id, 225 "xyz.MyInterface", "GetAll", ""); 226}); 227``` 228 229All bmcweb routes should handle 404 (not found) properly, and return it where 230appropriate. 500 internal error is not a substitute for this, and should be only 231used if there isn't a more appropriate error code that can be returned. This is 232important, because a number of vulnerability scanners attempt injection attacks 233in the form of `/myendpoint/foobar`, or `/myendpoint/#$*(%)&#%$)(\*&` in an 234attempt to circumvent security. If the server returns 500 to any of these 235requests, the security scanner logs it as an error for followup. While in 236general these errors are benign, and not actually a real security threat, having 237a clean security run allows maintainers to minimize the amount of time spent 238triaging issues reported from these scanning tools. 239 240An implementation of the above that handles 404 would look like: 241 242```cpp 243BMCWEB_ROUTE("/myendpoint/<str>", 244 [](Request& req, Response& res, const std::string& id){ 245 crow::connections::systemBus->async_method_call( 246 [asyncResp](const boost::system::error_code& ec, 247 const std::string& myProperty) { 248 if (ec == <error code that gets returned by not found>){ 249 messages::resourceNotFound(res); 250 return; 251 } 252 if (ec) 253 { 254 messages::internalError(asyncResp->res); 255 return; 256 } 257 ... handle code 258 }, 259 "xyz.openbmc_project.Logging", 260 "/xyz/openbmc_project/mypath/" + id, 261 "xyz.MyInterface", "GetAll", ""); 262}); 263``` 264 265Note: A more general form of this rule is that no handler should ever return 500 266on a working system, and any cases where 500 is found, can immediately be 267assumed to be 268[a bug in either the system, or bmcweb.](https://github.com/openbmc/bmcweb/blob/master/DEVELOPING.md#error-handling) 269 270## 12. Imprecise matching 271 272```cpp 273void isInventoryPath(const std::string& path){ 274 if (path.find("inventory")){ 275 return true; 276 } 277 return false; 278} 279``` 280 281When matching dbus paths, HTTP fields, interface names, care should be taken to 282avoid doing direct string containment matching. Doing so can lead to errors 283where fan1 and fan11 both report to the same object, and cause behavior breaks 284in subtle ways. 285 286When using dbus paths, rely on the methods on `sdbusplus::message::object_path`. 287When parsing HTTP field and lists, use the RFC7230 implementations from 288boost::beast. 289 290Other commonly misused methods are: boost::iequals. Unless the standard you're 291implementing (as is the case in some HTTP fields) requires case insensitive 292comparisons, casing should be obeyed, especially when relying on user-driven 293data. 294 295- boost::starts_with 296- boost::ends_with 297- std::string::starts_with 298- std::string::ends_with 299- std::string::rfind 300 301The above methods tend to be misused to accept user data and parse various 302fields from it. In practice, there tends to be better, purpose built methods for 303removing just the field you need. 304 305## 13. Complete replacement of the response object 306 307```cpp 308void getMembers(crow::Response& res){ 309 res.jsonValue = {{"Value", 2}}; 310} 311``` 312 313In many cases, bmcweb is doing multiple async actions in parallel, and 314orthogonal keys within the Response object might be filled in from another task. 315Completely replacing the json object can lead to convoluted situations where the 316output of the response is dependent on the _order_ of the asynchronous actions 317completing, which cannot be guaranteed, and has many time caused bugs. 318 319```cpp 320void getMembers(crow::Response& res){ 321 res.jsonValue["Value"] = 2; 322} 323``` 324 325As an added benefit, this code is also more efficient, as it avoids the 326intermediate object construction and the move, and as a result, produces smaller 327binaries. 328 329Note, another form of this error involves calling nlohmann::json::reset(), to 330clear an object that's already been filled in. This has the potential to clear 331correct data that was already filled in from other sources. 332 333## 14. Very long lambda callbacks 334 335```cpp 336dbus::utility::getSubTree("/", interfaces, 337 [asyncResp](boost::system::error_code& ec, 338 MapperGetSubTreeResult& res){ 339 <many lines of code> 340 }) 341``` 342 343Inline lambdas, while useful in some contexts, are difficult to read, and have 344inconsistent formatting with tools like clang-format, which causes significant 345problems in review, and in applying patchsets that might have minor conflicts. 346In addition, because they are declared in a function scope, they are difficult 347to unit test, and produce log messages that are difficult to read given their 348unnamed nature. 349 350Prefer to either use std::bind_front, and a normal method to handle the return, 351or a lambda that is less than 10 lines of code to handle an error inline. In 352cases where std::bind_front cannot be used, such as in 353sdbusplus::asio::connection::async_method_call, keep the lambda length less than 35410 lines, and call the appropriate function for handling non-trivial transforms. 355 356```cpp 357void afterGetSubTree(std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 358 boost::system::error_code& ec, 359 MapperGetSubTreeResult& res){ 360 <many lines of code> 361} 362 363dbus::utility::getSubTree("/xyz/openbmc_project/inventory", interfaces, 364 std::bind_front(afterGetSubTree, asyncResp)); 365``` 366 367See also the 368[Cpp Core Guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#f11-use-an-unnamed-lambda-if-you-need-a-simple-function-object-in-one-place-only) 369for generalized guidelines on when lambdas are appropriate. The above 370recommendation is aligned with the C++ Core Guidelines. 371