Like chdir(), but it keeps track of the current directory (in the * global "curr_dir"), and ensures that the path size doesn't overflow. * Also cleans the path using the clean_fname() function. */
| 1132 | * global "curr_dir"), and ensures that the path size doesn't overflow. |
| 1133 | * Also cleans the path using the clean_fname() function. */ |
| 1134 | int change_dir(const char *dir, int set_path_only) |
| 1135 | { |
| 1136 | extern int am_daemon, am_chrooted; |
| 1137 | static int initialised, skipped_chdir; |
| 1138 | unsigned int len; |
| 1139 | |
| 1140 | if (!initialised) { |
| 1141 | initialised = 1; |
| 1142 | if (getcwd(curr_dir, sizeof curr_dir - 1) == NULL) { |
| 1143 | rsyserr(FERROR, errno, "getcwd()"); |
| 1144 | exit_cleanup(RERR_FILESELECT); |
| 1145 | } |
| 1146 | curr_dir_len = strlen(curr_dir); |
| 1147 | } |
| 1148 | |
| 1149 | if (!dir) /* this call was probably just to initialize */ |
| 1150 | return 0; |
| 1151 | |
| 1152 | len = strlen(dir); |
| 1153 | if (len == 1 && *dir == '.' && (!skipped_chdir || set_path_only)) |
| 1154 | return 1; |
| 1155 | |
| 1156 | if (*dir == '/') { |
| 1157 | if (len >= sizeof curr_dir) { |
| 1158 | errno = ENAMETOOLONG; |
| 1159 | return 0; |
| 1160 | } |
| 1161 | if (!set_path_only && chdir(dir)) |
| 1162 | return 0; |
| 1163 | skipped_chdir = set_path_only; |
| 1164 | memcpy(curr_dir, dir, len + 1); |
| 1165 | } else { |
| 1166 | unsigned int save_dir_len = curr_dir_len; |
| 1167 | if (curr_dir_len + 1 + len >= sizeof curr_dir) { |
| 1168 | errno = ENAMETOOLONG; |
| 1169 | return 0; |
| 1170 | } |
| 1171 | if (!(curr_dir_len && curr_dir[curr_dir_len-1] == '/')) |
| 1172 | curr_dir[curr_dir_len++] = '/'; |
| 1173 | memcpy(curr_dir + curr_dir_len, dir, len + 1); |
| 1174 | |
| 1175 | if (!set_path_only) { |
| 1176 | int chdir_failed; |
| 1177 | /* In the daemon-without-chroot deployment we must not |
| 1178 | * follow a symlink in any component of the chdir |
| 1179 | * target -- otherwise CWD escapes the module and |
| 1180 | * every subsequent path-relative syscall (open, |
| 1181 | * chmod, lchown, ...) inherits the escape, which |
| 1182 | * defeats secure_relative_open's RESOLVE_BENEATH |
| 1183 | * anchor and re-opens the CVE-2026-29518 class of |
| 1184 | * symlink TOCTOU attacks. Use the secure resolver |
| 1185 | * to get a confined dirfd, then fchdir() to it. |
| 1186 | * |
| 1187 | * If skipped_chdir is set, a previous CD_SKIP_CHDIR |
| 1188 | * call buffered an absolute prefix in curr_dir |
| 1189 | * (e.g. change_pathname's CD_SKIP_CHDIR to orig_dir) |
| 1190 | * without syncing the kernel's CWD. Resolve `dir` |
| 1191 | * relative to that prefix as basedir so the secure |
no test coverage detected