[PATCH] sftp: don't clobber permissions of an existing local directory on download

Muhammad Bilal meatuni001 at gmail.com
Fri Aug 28 08:08:43 AEST 2026


download_dir_internal() in sftp-client.c calls mkdir(dst, tmpmode) and
ignores EEXIST, but never records whether it actually created the
directory. At the end of the function it unconditionally does:

    if (mode != tmpmode && chmod(dst, mode) == -1)

where 'mode' is the remote directory's permissions. This runs even
when 'dst' already existed locally and -p/preserve was not requested,
so 'sftp get -r' into an existing local directory silently rewrites
that directory's mode to match the remote side.

upload_dir_internal() and crossload_dir_internal() already guard their
equivalent remote-side sftp_setstat() call with a 'created' flag added
for bz3925 ("don't clobber the remote directory permissions unless
either we created the directory during the transfer or the -p flag
was set"); download_dir_internal() was missed. This gives it the same
guard: only chmod(2) the local directory if this call created it, or
if -p/preserve_flag was explicitly requested.

Verified with a local loopback sftp/sftp-server session
(sftp -D ./sftp-server): before the fix, 'get -r' into a pre-existing
local directory whose mode didn't already include the owner
write/execute bits (so mode != tmpmode) rewrote that directory's mode
to the remote's; after the fix it is left alone unless -p is given or
the directory was newly created, matching upload's behaviour.
---
 sftp-client.c | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/sftp-client.c b/sftp-client.c
index 1f031128e..74dd434ca 100644
--- a/sftp-client.c
+++ b/sftp-client.c
@@ -1882,7 +1882,7 @@ download_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
     int depth, Attrib *dirattrib, int preserve_flag, int print_flag,
     int resume_flag, int fsync_flag, int follow_link_flag, int inplace_flag)
 {
-	int i, ret = 0;
+	int i, ret = 0, created = 0;
 	SFTP_DIRENT **dir_entries;
 	char *filename, *new_src = NULL, *new_dst = NULL;
 	mode_t mode = 0777, tmpmode = mode;
@@ -1917,10 +1917,13 @@ download_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
 		    "did not send permissions", dst);
 	}
 
-	if (mkdir(dst, tmpmode) == -1 && errno != EEXIST) {
-		error("mkdir %s: %s", dst, strerror(errno));
-		return -1;
-	}
+	if (mkdir(dst, tmpmode) == -1) {
+		if (errno != EEXIST) {
+			error("mkdir %s: %s", dst, strerror(errno));
+			return -1;
+		}
+	} else
+		created = 1;
 
 	if (sftp_readdir(conn, src, &dir_entries) == -1) {
 		error("remote readdir \"%s\" failed", src);
@@ -1989,7 +1992,8 @@ download_dir_internal(struct sftp_conn *conn, const char *src, const char *dst,
 			    "\"%s\"", dst);
 	}
 
-	if (mode != tmpmode && chmod(dst, mode) == -1)
+	if ((created || preserve_flag) && mode != tmpmode &&
+	    chmod(dst, mode) == -1)
 		error("local chmod directory \"%s\": %s", dst,
 		    strerror(errno));
 
-- 
2.43.0



More information about the openssh-unix-dev mailing list