Secure version of mkstemp that prevents symlink attacks on parent directories. Like secure_relative_open(), this walks the path checking each component with O_NOFOLLOW to prevent TOCTOU race conditions. The template may be relative or absolute, but must not contain ../ components. Returns fd on success, -1 on error. */
| 2021 | Returns fd on success, -1 on error. |
| 2022 | */ |
| 2023 | int secure_mkstemp(char *template, mode_t perms) |
| 2024 | { |
| 2025 | #if !defined(O_NOFOLLOW) || !defined(O_DIRECTORY) || !defined(AT_FDCWD) |
| 2026 | /* Fall back to regular mkstemp on old systems */ |
| 2027 | return do_mkstemp(template, perms); |
| 2028 | #else |
| 2029 | char *lastslash; |
| 2030 | int dirfd = AT_FDCWD; |
| 2031 | int fd = -1; |
| 2032 | |
| 2033 | if (!template) { |
| 2034 | errno = EINVAL; |
| 2035 | return -1; |
| 2036 | } |
| 2037 | if (strncmp(template, "../", 3) == 0 || strstr(template, "/../")) { |
| 2038 | errno = EINVAL; |
| 2039 | return -1; |
| 2040 | } |
| 2041 | |
| 2042 | /* For absolute paths, start the secure walk from "/" rather than CWD. */ |
| 2043 | if (template[0] == '/') { |
| 2044 | dirfd = open("/", O_RDONLY | O_DIRECTORY | O_NOFOLLOW); |
| 2045 | if (dirfd < 0) |
| 2046 | return -1; |
| 2047 | } |
| 2048 | |
| 2049 | /* Find the last slash to separate directory from filename */ |
| 2050 | lastslash = strrchr(template, '/'); |
| 2051 | if (lastslash) { |
| 2052 | char *path_copy = my_strdup(template, __FILE__, __LINE__); |
| 2053 | if (!path_copy) |
| 2054 | return -1; |
| 2055 | |
| 2056 | /* Null-terminate at the last slash to get directory part */ |
| 2057 | path_copy[lastslash - template] = '\0'; |
| 2058 | |
| 2059 | /* Walk the directory path securely */ |
| 2060 | for (const char *part = strtok(path_copy, "/"); |
| 2061 | part != NULL; |
| 2062 | part = strtok(NULL, "/")) |
| 2063 | { |
| 2064 | int next_fd = openat(dirfd, part, O_RDONLY | O_DIRECTORY | O_NOFOLLOW); |
| 2065 | if (next_fd == -1) { |
| 2066 | int save_errno = errno; |
| 2067 | free(path_copy); |
| 2068 | if (dirfd != AT_FDCWD) close(dirfd); |
| 2069 | errno = (save_errno == ELOOP) ? ELOOP : save_errno; |
| 2070 | return -1; |
| 2071 | } |
| 2072 | if (dirfd != AT_FDCWD) close(dirfd); |
| 2073 | dirfd = next_fd; |
| 2074 | } |
| 2075 | free(path_copy); |
| 2076 | } |
| 2077 | |
| 2078 | /* Now create the temp file in the securely-opened directory */ |
| 2079 | perms |= S_IWUSR; |
| 2080 |
no test coverage detected