| 1431 | } |
| 1432 | |
| 1433 | static int private_directory_tree_open(const char *directory_path) { |
| 1434 | if (!directory_path || !directory_path[0] || O_DIRECTORY == 0 || O_NOFOLLOW == 0) { |
| 1435 | return -1; |
| 1436 | } |
| 1437 | char *path = private_log_directory_path_copy(directory_path); |
| 1438 | if (!path) { |
| 1439 | return -1; |
| 1440 | } |
| 1441 | bool absolute = path[0] == '/'; |
| 1442 | int current_fd = open(absolute ? "/" : ".", O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW); |
| 1443 | bool ok = current_fd >= 0 && fd_set_cloexec(current_fd); |
| 1444 | char *cursor = path; |
| 1445 | while (ok && *cursor == '/') { |
| 1446 | cursor++; |
| 1447 | } |
| 1448 | bool visited = false; |
| 1449 | while (ok && *cursor) { |
| 1450 | char *component = cursor; |
| 1451 | while (*cursor && *cursor != '/') { |
| 1452 | cursor++; |
| 1453 | } |
| 1454 | char saved = *cursor; |
| 1455 | *cursor = '\0'; |
| 1456 | if (strcmp(component, ".") == 0) { |
| 1457 | /* Relative paths may contain a harmless explicit current-dir |
| 1458 | * component. Parent traversal is never valid for private logs. */ |
| 1459 | } else if (strcmp(component, "..") == 0 || !component[0]) { |
| 1460 | ok = false; |
| 1461 | } else { |
| 1462 | ok = posix_directory_parent_secure(current_fd); |
| 1463 | if (!ok) { |
| 1464 | /* #1537: this branch used to leave the detail empty, so the |
| 1465 | * caller fell back to printing errno — which NOTHING here sets. |
| 1466 | * A reporter was handed "errno 2" (ENOENT) for a permission |
| 1467 | * refusal and went looking for a missing file that existed. |
| 1468 | * An unset errno is not a diagnosis. |
| 1469 | * |
| 1470 | * The first version of that fix then named the WRONG directory. |
| 1471 | * posix_directory_parent_secure() validates current_fd — the |
| 1472 | * directory we are already in — but the message printed |
| 1473 | * `component`, the child about to be entered. So #1537 read |
| 1474 | * "ancestor '.cache'" when /Users/<user> was refusing, and |
| 1475 | * #1621 read "cbm-daemon-501" when /private/tmp was. Both |
| 1476 | * reporters inspected a directory that was not the one |
| 1477 | * refusing, found it clean, and said so — correctly. Naming the |
| 1478 | * containing directory is the difference between a report we |
| 1479 | * can act on and weeks of talking past each other. */ |
| 1480 | ipc_validation_detail_set( |
| 1481 | "%s: the directory CONTAINING '%s' is not a usable private-directory parent " |
| 1482 | "(it must be owned by you, not world-writable, and carry no allow-ACL). Check " |
| 1483 | "that containing directory, not '%s' itself", |
| 1484 | directory_path, component, component); |
| 1485 | } |
| 1486 | bool created = ok && mkdirat(current_fd, component, 0700) == 0; |
| 1487 | if (!created && errno != EEXIST) { |
| 1488 | ok = false; |
| 1489 | } |
| 1490 | int next_fd = |
no test coverage detected