xref: /openbmc/bmcweb/COMMON_ERRORS.md (revision 9d424669)
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```C++
14int myBadMethod(const nlohmann::json& j){
15    const int* myPtr = j.get_if<int>();
16    return *myPtr;
17}
18```
19This pointer is not guaranteed to be filled, and could be a null dereference.
20
21### 2. String views aren't null terminated
22```C++
23int getIntFromString(const std::string_view s){
24    return std::atoi(s.data());
25}
26```
27This will give the right answer much of the time, but has the possibility to
28fail when string\_view is not null terminated.  Use from\_chars instead, which
29takes both a pointer and a length
30
31### 3. Not handling input errors
32```C++
33int getIntFromString(const std::string& s){
34    return std::atoi(s.c_str());
35}
36```
37In the case where the string is not representable as an int, this will trigger
38undefined behavior at system level.  Code needs to check for validity of the
39string, ideally with something like from\_chars, and return the appropriate error
40code.
41
42### 4. Walking off the end of a string
43```C++
44std::string getFilenameFromPath(const std::string& path){
45    size_t index = path.find("/");
46    if (index != std::string::npos){
47        // If the string ends with "/", this will walk off the end of the string.
48        return path.substr(pos + 1);
49    }
50    return "";
51}
52```
53
54### 5. Using methods that throw (or not handling bad inputs)
55```C++
56int myBadMethod(nlohmann::json& j){
57    return j.get<int>();
58}
59```
60This method throws, and bad inputs will not be handled
61
62Commonly used methods that fall into this pattern:
63
64- std::variant::get
65- std::vector::at
66- std::map::at
67- std::set::at
68- std::\<generic container type\>::at
69- nlohmann::json::operator!=
70- nlohmann::json::operator+=
71- nlohmann::json::at
72- nlohmann::json::get
73- nlohmann::json::get\_ref
74- nlohmann::json::get\_to
75- std::filesystem::create\_directory
76- std::filesystem::rename
77- std::filesystem::file\_size
78- std::stoi
79- std::stol
80- std::stoll
81
82#### special/strange case:
83
84nlohmann::json::parse by default throws on failure, but also accepts a optional
85argument that causes it to not throw.  Please consult the other examples in the
86code for how to handle errors.
87
88
89#### Special note: Boost
90there is a whole class of boost asio functions that provide both a method that
91throws on failure, and a method that accepts and returns an error code.  This is
92not a complete list, but users should verify in the boost docs when calling into
93asio methods, and prefer the one that returns an error code instead of throwing.
94
95- boost::asio::ip::tcp::acceptor::bind();
96- boost::asio::ip::tcp::acceptor::cancel();
97- boost::asio::ip::tcp::acceptor::close();
98- boost::asio::ip::tcp::acceptor::listen();
99- boost::asio::ip::address::make\_address();
100
101### 6. Blocking functions
102
103bmcweb uses a single reactor for all operations.  Blocking that reactor for any
104amount of time causes all other operations to stop.  The common blocking
105functions that tend to be called incorrectly are:
106
107- sleep()
108- boost::asio::ip::tcp::socket::read()
109- boost::asio::ip::tcp::socket::read\_some()
110- boost::asio::ip::tcp::socket::write()
111- boost::asio::ip::tcp::socket::write\_some()
112- boost::asio::ip::tcp::socket::connect()
113- boost::asio::ip::tcp::socket::send()
114- boost::asio::ip::tcp::socket::wait()
115- boost::asio::steady\_timer::wait()
116
117Note: an exception is made for filesystem/disk IO read and write.  This is
118mostly due to not having great abstractions for it that mate well with the async
119system, the fact that most filesystem accesses are into tmpfs (and therefore
120should be "fast" most of the time) and in general how little the filesystem is
121used in practice.
122
123### 7. Lack of locking between subsequent calls
124While global data structures are discouraged, they are sometimes required to
125store temporary state for operations that require it.  Given the single
126threaded nature of bmcweb, they are not required to be explicitly threadsafe,
127but they must be always left in a valid state, and checked for other uses
128before occupying.
129
130```C++
131std::optional<std::string> currentOperation;
132void firstCallbackInFlow(){
133    currentOperation = "Foo";
134}
135void secondCallbackInFlow(){
136    currentOperation.reset();
137}
138```
139
140In the above case, the first callback needs a check to ensure that
141currentOperation is not already being used.
142
143### 8. Wildcard reference captures in lambdas
144```
145std::string x; auto mylambda = [&](){
146    x = "foo";
147}
148do_async_read(mylambda)
149```
150
151Numerous times, lifetime issues of const references have been injected into
152async bmcweb code.  While capturing by reference can be useful, given how
153difficult these types of bugs are to triage, bmcweb explicitly requires that all
154code captures variables by name explicitly, and calls out each variable being
155captured by value or by reference.  The above prototypes would change to
156[&x]()... Which makes clear that x is captured, and its lifetime needs tracked.
157
158
159### 9. URLs should end in "/"
160```C++
161BMCWEB("/foo/bar");
162```
163Unless you explicitly have a reason not to (as there is one known exception
164where the behavior must differ) all URL handlers should end in "/".  The bmcweb
165route handler will detect routes ending in slash and generate routes for both
166the route ending in slash and the one without.  This allows both URLs to be
167used by users.  While many specifications do not require this, it resolves a
168whole class of bug that we've seen in the past.
169
170
171### 10. URLs constructed in aggregate
172```C++
173std::string routeStart = "/redfish/v1";
174
175BMCWEB_ROUTE(routestart + "/SessionService/")
176```
177Very commonly, bmcweb maintainers and contributors alike have to do audits of
178all routes that are available, to verify things like security and documentation
179accuracy.  While these processes are largely manual, they can mostly be
180conducted by a simple grep statement to search for urls in question.  Doing the
181above makes the route handlers no longer greppable, and complicates bmcweb
182patchsets as a whole.
183
184### 11. Not responding to 404
185```C++
186BMCWEB_ROUTE("/myendpoint/<str>",
187    [](Request& req, Response& res, const std::string& id){
188     crow::connections::systemBus->async_method_call(
189          [asyncResp](const boost::system::error_code ec,
190                      const std::string& myProperty) {
191              if (ec)
192              {
193                  messages::internalError(asyncResp->res);
194                  return;
195              }
196              ... handle code
197          },
198          "xyz.openbmc_project.Logging",
199          "/xyz/openbmc_project/mypath/" + id,
200          "xyz.MyInterface", "GetAll", "");
201});
202```
203All bmcweb routes should handle 404 (not found) properly, and return it where
204appropriate.  500 internal error is not a substitute for this, and should be
205only used if there isn't a more appropriate error code that can be returned.
206This is important, because a number of vulnerability scanners attempt injection
207attacks in the form of /myendpoint/foobar, or /myendpoint/#$*(%)&#%$)(*&  in an
208attempt to circumvent security.  If the server returns 500 to any of these
209requests, the security scanner logs it as an error for followup.  While in
210general these errors are benign, and not actually a real security threat, having
211a clean security run allows maintainers to minimize the amount of time spent
212triaging issues reported from these scanning tools.
213
214An implementation of the above that handles 404 would look like:
215```C++
216BMCWEB_ROUTE("/myendpoint/<str>",
217    [](Request& req, Response& res, const std::string& id){
218     crow::connections::systemBus->async_method_call(
219          [asyncResp](const boost::system::error_code ec,
220                      const std::string& myProperty) {
221              if (ec == <error code that gets returned by not found>){
222                  messages::resourceNotFound(res);
223                  return;
224              }
225              if (ec)
226              {
227                  messages::internalError(asyncResp->res);
228                  return;
229              }
230              ... handle code
231          },
232          "xyz.openbmc_project.Logging",
233          "/xyz/openbmc_project/mypath/" + id,
234          "xyz.MyInterface", "GetAll", "");
235});
236```
237
238Note: A more general form of this rule is that no handler should ever return 500
239on a working system, and any cases where 500 is found, can immediately be
240assumed to be [a bug in either the system, or bmcweb.](https://github.com/openbmc/bmcweb/blob/master/DEVELOPING.md#error-handling)
241
242### 12. Imprecise matching
243```C++
244void isInventoryPath(const std::string& path){
245    if (path.find("inventory")){
246        return true;
247    }
248    return false;
249}
250```
251When matching dbus paths, HTTP fields, interface names, care should be taken to
252avoid doing direct string containment matching.  Doing so can lead to errors
253where fan1 and fan11 both report to the same object, and cause behavior breaks
254in subtle ways.
255
256When using dbus paths, rely on the methods on sdbusplus::message::object\_path.
257When parsing HTTP field and lists, use the RFC7230 implementations from
258boost::beast.
259
260Other commonly misused methods are:
261boost::iequals.  Unless the standard you're implementing (as is the case in some
262HTTP fields) requires case insensitive comparisons, casing should be obeyed,
263especially when relying on user-driven data.
264
265- boost::starts\_with
266- boost::ends\_with
267- std::string::starts\_with
268- std::string::ends\_with
269- std::string::rfind
270
271The above methods tend to be misused to accept user data and parse various
272fields from it.  In practice, there tends to be better, purpose built methods
273for removing just the field you need.
274