From 3ae6a69154a52aa3e14ffaf2a44abe9b9c409edb Mon Sep 17 00:00:00 2001 From: Rani <33231841+ImTheCurse@users.noreply.github.com> Date: Fri, 3 Oct 2025 21:20:52 +0300 Subject: [PATCH] Bugfix(sftp): fix loose permissions on sftp backend. (#5497) --- changelog/unreleased/issue-5487 | 8 ++++++++ internal/backend/sftp/sftp.go | 18 +++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 changelog/unreleased/issue-5487 diff --git a/changelog/unreleased/issue-5487 b/changelog/unreleased/issue-5487 new file mode 100644 index 000000000..ddac79d10 --- /dev/null +++ b/changelog/unreleased/issue-5487 @@ -0,0 +1,8 @@ +Bugfix: Mark files as readonly when using the SFTP backend + +Files created by the SFTP backend previously allowed writes to those files. +Restic now restricts the file permissions on SFTP backend to readonly. +This change only has an effect for sftp servers with support for the chmod operation. + +https://github.com/restic/restic/issues/5487 +https://github.com/restic/restic/pull/5497 diff --git a/internal/backend/sftp/sftp.go b/internal/backend/sftp/sftp.go index 269a9f41a..55f92ec7b 100644 --- a/internal/backend/sftp/sftp.go +++ b/internal/backend/sftp/sftp.go @@ -287,6 +287,18 @@ func tempSuffix() string { return hex.EncodeToString(nonce[:]) } +func setFileReadonly(client *sftp.Client, path string, mode os.FileMode) error { + // clear owner/group/other write bits + readonlyMode := mode &^ 0o222 + err := client.Chmod(path, readonlyMode) + + // if the operation is not supported in the sftp server we ignore it. + if errors.Is(err, sftp.ErrSSHFxOpUnsupported) { + return nil + } + return err +} + // Save stores data in the backend at the handle. func (r *SFTP) Save(_ context.Context, h backend.Handle, rd backend.RewindReader) error { if err := r.clientError(); err != nil { @@ -350,7 +362,6 @@ func (r *SFTP) Save(_ context.Context, h backend.Handle, rd backend.RewindReader _ = f.Close() return errors.Errorf("Write %v: wrote %d bytes instead of the expected %d bytes", tmpFilename, wbytes, rd.Length()) } - err = f.Close() if err != nil { return errors.Wrapf(err, "Close %v", tmpFilename) @@ -362,6 +373,11 @@ func (r *SFTP) Save(_ context.Context, h backend.Handle, rd backend.RewindReader } else { err = r.c.Rename(tmpFilename, filename) } + err = setFileReadonly(r.c, filename, r.Modes.File) + if err != nil { + return errors.Errorf("sftp setFileReadonly: %v", err) + } + return errors.Wrapf(err, "Rename %v", tmpFilename) }