| 1789 | unsigned int curr_dir_len; |
| 1790 | |
| 1791 | int secure_relative_open(const char *basedir, const char *relpath, int flags, mode_t mode) |
| 1792 | { |
| 1793 | extern int am_daemon, am_chrooted; |
| 1794 | extern char *module_dir; |
| 1795 | extern unsigned int module_dirlen; |
| 1796 | char modrel_buf[MAXPATHLEN]; |
| 1797 | int reanchored = 0; |
| 1798 | |
| 1799 | if (!relpath || relpath[0] == '/') { |
| 1800 | // must be a relative path |
| 1801 | errno = EINVAL; |
| 1802 | return -1; |
| 1803 | } |
| 1804 | |
| 1805 | /* Sanitizing daemon only (am_daemon && !am_chrooted). Here we have chdir'd |
| 1806 | * into a sub-dir of the module (the transfer destination), so a relative |
| 1807 | * alt-dest like "../01" may legitimately climb to a sibling that is still |
| 1808 | * inside the module (#915). Confining beneath the cwd would reject that |
| 1809 | * climb. Re-anchor at the module root -- the real trust boundary -- by |
| 1810 | * prefixing the cwd's module-relative path (from rsync's logical curr_dir[], |
| 1811 | * a guaranteed lexical prefix of module_dir, unlike getcwd()) and resolving |
| 1812 | * beneath module_dir; RESOLVE_BENEATH then allows in-module climbs and still |
| 1813 | * rejects escapes. Only for paths that contain "..". module_dirlen is 0 for |
| 1814 | * a `path = /` module (clientserver.c), so we gate on module_dir, not its |
| 1815 | * length, to cover that case too -- the prefix check below treats |
| 1816 | * module_dirlen 0 as "module root is /". */ |
| 1817 | if (am_daemon && !am_chrooted |
| 1818 | && module_dir && module_dir[0] == '/' |
| 1819 | && (basedir == NULL || basedir[0] != '/') |
| 1820 | && (path_has_dotdot_component(relpath) |
| 1821 | || (basedir && path_has_dotdot_component(basedir)))) { |
| 1822 | const char *p; |
| 1823 | int n; |
| 1824 | if (curr_dir_len >= module_dirlen |
| 1825 | && strncmp(curr_dir, module_dir, module_dirlen) == 0 |
| 1826 | && (curr_dir[module_dirlen] == '\0' || curr_dir[module_dirlen] == '/')) { |
| 1827 | for (p = curr_dir + module_dirlen; *p == '/'; p++) {} |
| 1828 | if (basedir) |
| 1829 | n = snprintf(modrel_buf, sizeof modrel_buf, "%s%s%s/%s", |
| 1830 | p, *p ? "/" : "", basedir, relpath); |
| 1831 | else |
| 1832 | n = snprintf(modrel_buf, sizeof modrel_buf, "%s%s%s", |
| 1833 | p, *p ? "/" : "", relpath); |
| 1834 | if (n < 0 || n >= (int)sizeof modrel_buf) { |
| 1835 | errno = ENAMETOOLONG; |
| 1836 | return -1; |
| 1837 | } |
| 1838 | basedir = module_dir; /* absolute, operator-trusted anchor */ |
| 1839 | relpath = modrel_buf; |
| 1840 | reanchored = 1; |
| 1841 | } |
| 1842 | /* else: cwd not under module root as expected -- fall through to the |
| 1843 | * front-door rejection below (fail safe). */ |
| 1844 | } |
| 1845 | |
| 1846 | /* Reject any path with a literal ".." component (bare "..", |
| 1847 | * "../foo", "foo/..", "foo/../bar", "subdir/.."). The previous |
| 1848 | * substring-based check caught only "../" prefix and "/../" |
no test coverage detected