| 1337 | } |
| 1338 | |
| 1339 | static char *private_log_directory_path_copy(const char *directory_path) { |
| 1340 | #ifdef __APPLE__ |
| 1341 | /* Darwin exposes the trusted top-level aliases /tmp -> /private/tmp and |
| 1342 | * /var -> /private/var. Resolve only those root-owned aliases before the |
| 1343 | * component-wise O_NOFOLLOW walk. Canonicalizing the complete caller path |
| 1344 | * would follow an attacker-controlled cache/log symlink and is forbidden. */ |
| 1345 | static const char *const aliases[] = {"/tmp", "/var"}; |
| 1346 | for (size_t index = 0; index < sizeof(aliases) / sizeof(aliases[0]); index++) { |
| 1347 | const char *alias = aliases[index]; |
| 1348 | size_t alias_length = strlen(alias); |
| 1349 | if (strncmp(directory_path, alias, alias_length) != 0 || |
| 1350 | (directory_path[alias_length] != '\0' && directory_path[alias_length] != '/')) { |
| 1351 | continue; |
| 1352 | } |
| 1353 | struct stat alias_status; |
| 1354 | if (lstat(alias, &alias_status) != 0 || !S_ISLNK(alias_status.st_mode)) { |
| 1355 | break; |
| 1356 | } |
| 1357 | struct stat root_status; |
| 1358 | char resolved[CBM_DAEMON_IPC_PATH_CAP]; |
| 1359 | if (alias_status.st_uid != 0 || lstat("/", &root_status) != 0 || |
| 1360 | !S_ISDIR(root_status.st_mode) || root_status.st_uid != 0 || |
| 1361 | (root_status.st_mode & 0022) != 0 || !realpath(alias, resolved)) { |
| 1362 | return NULL; |
| 1363 | } |
| 1364 | struct stat resolved_status; |
| 1365 | if (lstat(resolved, &resolved_status) != 0 || !S_ISDIR(resolved_status.st_mode) || |
| 1366 | resolved_status.st_uid != 0) { |
| 1367 | return NULL; |
| 1368 | } |
| 1369 | return string_format("%s%s", resolved, directory_path + alias_length); |
| 1370 | } |
| 1371 | #endif |
| 1372 | return string_copy(directory_path); |
| 1373 | } |
| 1374 | |
| 1375 | static bool posix_directory_owner_trusted(uid_t owner) { |
| 1376 | return owner == (uid_t)0 || owner == geteuid(); |
no test coverage detected