diff options
| author | Lena <lena@omega> | 2026-08-16 00:00:00 +0000 |
|---|---|---|
| committer | Lena <lena@omega> | 2026-08-16 00:00:00 +0000 |
| commit | beaa0c970d6c3538119b8a34a3f2f754b45e54cf (patch) | |
| tree | 0a4be0a63a5c5b40f3db66a7e2b6c603e60d8473 /rsh/e2e_test.go | |
| parent | 0890d139e5fa501561e469aca7854791a936fcb6 (diff) | |
| download | rsend-beaa0c970d6c3538119b8a34a3f2f754b45e54cf.tar.gz | |
native: harden rsync and SSH transport
Update rsync to 3.5.0 and Go to 1.26.6. Bound SSH handshakes, pin
host-key types, and build 16 KB-aligned hardened executables.
Diffstat (limited to 'rsh/e2e_test.go')
| -rw-r--r-- | rsh/e2e_test.go | 64 |
1 files changed, 54 insertions, 10 deletions
diff --git a/rsh/e2e_test.go b/rsh/e2e_test.go index 88cd317..e6f82b9 100644 --- a/rsh/e2e_test.go +++ b/rsh/e2e_test.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "io" "os" "os/exec" "path/filepath" @@ -28,26 +29,69 @@ func TestEndToEndRealRsync(t *testing.T) { } keyPath, pub := genClientKey(t) - srv := newTestServer(t, pub, shellExec) + remoteRoot := t.TempDir() + execute := func(cmd string, stdin io.Reader, stdout, stderr io.Writer) int { + return shellExecIn(remoteRoot, cmd, stdin, stdout, stderr) + } + srv := newTestServer(t, pub, execute) kh := writeKnownHosts(t, "127.0.0.1", srv.port(), srv.hostKey.PublicKey()) src := t.TempDir() - dst := t.TempDir() + // A normal rsync-over-SSH destination may contain shell punctuation. rsync + // escapes the remote command before handing it to rsh; this catches any + // transport change that loses those escapes while joining the command. + marker := filepath.Join(remoteRoot, "injected") + dst := filepath.Join(t.TempDir(), "backup path;literal$dollar'quote$(touch injected)") mustWrite(t, filepath.Join(src, "a.txt"), "alpha") mustWrite(t, filepath.Join(src, "sub", "b.bin"), "\x00\x01\x02\x03beta") mustWrite(t, filepath.Join(src, "sub", "c.txt"), "gamma gamma gamma") - cmd := exec.Command(rsyncBin, "-a", "-e", rshBin, src+"/", "u@127.0.0.1:"+dst+"/") - cmd.Env = append(os.Environ(), - "RSH_KEY="+keyPath, - "RSH_KNOWN_HOSTS="+kh, - "RSH_PORT="+strconv.Itoa(srv.port()), - ) - if out, err := cmd.CombinedOutput(); err != nil { - t.Fatalf("rsync: %v\n%s", err, out) + runRsync := func(mirror bool) { + t.Helper() + args := []string{ + "-rt", "--out-format=%o %n", "--partial-dir=.rsend-partial", "--timeout=300", + "--no-perms", "--no-owner", "--no-group", "--omit-dir-times", "-e", rshBin, + } + if mirror { + args = append(args, "--delete-after") + } + args = append(args, src+"/", "u@127.0.0.1:"+dst+"/") + cmd := exec.Command(rsyncBin, args...) + cmd.Env = append(os.Environ(), + "RSH_KEY="+keyPath, + "RSH_KNOWN_HOSTS="+kh, + "RSH_PORT="+strconv.Itoa(srv.port()), + ) + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("rsync: %v\n%s", err, out) + } } + runRsync(false) checkSame(t, src, dst) + if _, err := os.Stat(marker); !os.IsNotExist(err) { + t.Fatalf("remote path was evaluated by the shell: %s", marker) + } + + // An empty but readable source is a valid mirror. The app intentionally + // follows rsync here: every ordinary item in the destination is removed. + entries, err := os.ReadDir(src) + if err != nil { + t.Fatal(err) + } + for _, entry := range entries { + if err := os.RemoveAll(filepath.Join(src, entry.Name())); err != nil { + t.Fatal(err) + } + } + runRsync(true) + entries, err = os.ReadDir(dst) + if err != nil { + t.Fatal(err) + } + if len(entries) != 0 { + t.Fatalf("empty mirror left destination entries: %v", entries) + } } func mustWrite(t *testing.T, path, content string) { |