Stop using PATH_MAX

PATH_MAX isn't guaranteed to be defined in Posix environments; it is
only on systems that have a path length limit, and even in environments
where it is defined its usage can lead to issues.

To avoid using PATH_MAX, I've made two main changes:

- Where realpath() was used, I've changed the code to use its
  [POSIX.1-2008]'s new behaviour, where passing a null pointer as the
  resolved_name buffer results in realpath() to automatically allocate
  a buffer large enough to handle the given path, that is returned to
  the caller. This has been supported for a long time as a GNU libc
  extension before being standardized.
- Where readlink() was used, the size of the buffer was already
  determined when calling lstat(); the returned struct stat contains a
  st_size field, containing the number of bytes needed to store the
  symbolic link contents. This meant that to avoid using the tricky
  define I only needed to use a dynamically allocated buffer instead of
  a static one, of size stat.st_size (+1 when a null terminator is
  needed).

To make sure that memory is always freed, I've wrapped the new dynamic
allocations in an std::unique_ptr. The pointer returned by realpath()
must be freed with free(), so a unique_ptr with a custom deleter that
calls free() on destruction was used.

To read more about why PATH_MAX leads to buggy code I'd suggest reading
something like this: <https://eklitzke.org/path-max-is-tricky>.

[POSIX.1-2008]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/realpath.html
This commit is contained in:
Andrea Pappacoda 2022-10-10 23:36:03 +02:00 committed by Howard Hinnant
parent ab1dc3a5eb
commit ac0c58d5da

View File

@ -488,9 +488,10 @@ discover_tz_dir()
if (!(lstat(timezone, &sb) == 0 && S_ISLNK(sb.st_mode) && sb.st_size > 0)) if (!(lstat(timezone, &sb) == 0 && S_ISLNK(sb.st_mode) && sb.st_size > 0))
throw runtime_error("discover_tz_dir failed\n"); throw runtime_error("discover_tz_dir failed\n");
string result; string result;
char rp[PATH_MAX+1] = {}; unique_ptr<char[]> rp(new char[sb.st_size]);
if (readlink(timezone, rp, sizeof(rp)-1) > 0) const auto rp_length = readlink(timezone, rp.get(), sb.st_size);
result = string(rp); if (rp_length > 0)
result = string(rp.get(), rp_length); // readlink doesn't null-terminate
else else
throw system_error(errno, system_category(), "readlink() failed"); throw system_error(errno, system_category(), "readlink() failed");
auto i = result.find("zoneinfo"); auto i = result.find("zoneinfo");
@ -4026,10 +4027,10 @@ bool
sniff_realpath(const char* timezone) sniff_realpath(const char* timezone)
{ {
using namespace std; using namespace std;
char rp[PATH_MAX+1] = {}; unique_ptr<char, decltype(free) *> rp(realpath(timezone, nullptr), free);
if (realpath(timezone, rp) == nullptr) if (rp.get() == nullptr)
throw system_error(errno, system_category(), "realpath() failed"); throw system_error(errno, system_category(), "realpath() failed");
auto result = extract_tz_name(rp); auto result = extract_tz_name(rp.get());
return result != "posixrules"; return result != "posixrules";
} }
@ -4056,18 +4057,24 @@ tzdb::current_zone() const
{ {
using namespace std; using namespace std;
static const bool use_realpath = sniff_realpath(timezone); static const bool use_realpath = sniff_realpath(timezone);
char rp[PATH_MAX+1] = {};
if (use_realpath) if (use_realpath)
{ {
if (realpath(timezone, rp) == nullptr) unique_ptr<char, decltype(free) *> rp(realpath(timezone, nullptr), free);
if (rp.get() == nullptr)
throw system_error(errno, system_category(), "realpath() failed"); throw system_error(errno, system_category(), "realpath() failed");
return locate_zone(extract_tz_name(rp.get()));
} }
else else
{ {
if (readlink(timezone, rp, sizeof(rp)-1) <= 0) // +1 because st_size doesn't include the '\0' terminator
const auto rp_size = sb.st_size + 1;
unique_ptr<char[]> rp(new char[rp_size]);
const auto rp_length = readlink(timezone, rp.get(), rp_size);
if (rp_length <= 0)
throw system_error(errno, system_category(), "readlink() failed"); throw system_error(errno, system_category(), "readlink() failed");
rp.get()[rp_length] = '\0'; // readlink doesn't null-terminate
return locate_zone(extract_tz_name(rp.get()));
} }
return locate_zone(extract_tz_name(rp));
} }
} }
// On embedded systems e.g. buildroot with uclibc the timezone is linked // On embedded systems e.g. buildroot with uclibc the timezone is linked
@ -4086,9 +4093,10 @@ tzdb::current_zone() const
if (lstat(timezone, &sb) == 0 && S_ISLNK(sb.st_mode) && sb.st_size > 0) { if (lstat(timezone, &sb) == 0 && S_ISLNK(sb.st_mode) && sb.st_size > 0) {
using namespace std; using namespace std;
string result; string result;
char rp[PATH_MAX+1] = {}; unique_ptr<char[]> rp(new char[sb.st_size]);
if (readlink(timezone, rp, sizeof(rp)-1) > 0) const auto rp_length = readlink(timezone, rp.get(), sb.st_size);
result = string(rp); if (rp_length > 0)
result = string(rp.get(), rp_length); // readlink doesn't null-terminate
else else
throw system_error(errno, system_category(), "readlink() failed"); throw system_error(errno, system_category(), "readlink() failed");