After joining my personal WiFi with the SSID “%p%s%s%s%s%n”, my iPhone permanently disabled it’s WiFi functionality. Neither rebooting nor changing SSID fixes it :~)
— Carl Schou (@vm_call) on Twitter
The SSID format string bug in iOS WiFi service has been making rounds on social
media.
This blog post
nicely pinpoints the root cause to concatenating the SSID with a string
and passing the result to a logging method that uses it as a format string.
Let’s see how modern APIs like the ones of the {fmt} formatting library and C++20 std::format
can prevent this.
First, let’s adapt the problematic SSID "%p%s%s%s%s%n"
to the format string
syntax used by {fmt} which is based on Python’s format.
The %n
specifier where
n
stands for “notorious” is intentionally unsupported but it’s irrelevant
because the crash occurs when processing one of the %s
specifiers. Other than
that the translation is pretty straightforward: "{:p}{:s}{:s}{:s}{:s}"
.
Note that all of these specifiers can be omitted ("{}{}{}{}{}"
) as they
are equivalent to the defaults.
There are two problems in the original issue. The first one is that external data is passed as a format string. This is easily solved with compile-time format string validation, for example
std::string ssid = "{:p}{:s}{:s}{:s}{:s}";
fmt::print("SSID: " + ssid);
won’t even compile in C++20 (godbolot) because the format string is not known at compile time.
To trigger the bug one would have to explicitly circumvent the compile-time checks by opting into runtime ones (or using an older compiler):
std::string ssid = "{:p}{:s}{:s}{:s}{:s}";
fmt::print(fmt::runtime("SSID: " + ssid));
but even this will only throw an exception since the replacement fields like
{:p}
or {:s}
don’t have corresponding arguments.
This brings us to the second problem: WFLog:message:
doesn’t validate the
format string which results in a crash. This is terrible even if you only pass
trusted data to formatting functions because people are bad at testing logging
and error paths where format string bugs often creep in. As I wrote in my
previous post,
formatting APIs in pretty much all programming languages do such validation
nowadays with C being a notable exception.
Conclusion: format string bugs are easily preventable and the main reason we
still see issues like the one in iOS WiFi service in 2021 is legacy code.
Both {fmt} and C++20 std::format
eliminate this class of bugs via a
combination of compile-time checks enabled by default and runtime validation.
Last modified on 2021-06-22