From ecc3dcc819ac9cba2d27c574a5b01cfe77af197d Mon Sep 17 00:00:00 2001 From: Allan Roger Reid Date: Tue, 14 Oct 2025 14:53:42 -0700 Subject: [PATCH 1/3] handles .old file normalization --- apply.go | 54 +++++++++++++++++++++++++++++++++++------------ apply_test.go | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 13 deletions(-) diff --git a/apply.go b/apply.go index ec1b92a..a8142fc 100644 --- a/apply.go +++ b/apply.go @@ -27,10 +27,10 @@ func Apply(update io.Reader, opts Options) error { } // PrepareAndCheckBinary reads the new binary content from io.Reader and performs the following actions: -// 1. If configured, applies the contents of the update io.Reader as a binary patch. -// 2. If configured, computes the checksum of the executable and verifies it matches. -// 3. If configured, verifies the signature with a public key. -// 4. Creates a new file, /path/to/.target.new with the TargetMode with the contents of the updated file +// 1. If configured, applies the contents of the update io.Reader as a binary patch. +// 2. If configured, computes the checksum of the executable and verifies it matches. +// 3. If configured, verifies the signature with a public key. +// 4. Creates a new file, /path/to/.target.new with the TargetMode with the contents of the updated file func PrepareAndCheckBinary(update io.Reader, opts Options) error { // get target path targetPath, err := opts.getPath() @@ -88,12 +88,12 @@ func PrepareAndCheckBinary(update io.Reader, opts Options) error { // CommitBinary moves the new executable to the location of the current executable or opts.TargetPath // if specified. It performs the following operations: -// 1. Renames /path/to/target to /path/to/.target.old -// 2. Renames /path/to/.target.new to /path/to/target -// 3. If the final rename is successful, deletes /path/to/.target.old, returns no error. On Windows, -// the removal of /path/to/target.old always fails, so instead Apply hides the old file instead. -// 4. If the final rename fails, attempts to roll back by renaming /path/to/.target.old -// back to /path/to/target. +// 1. Renames /path/to/target to /path/to/.target.old +// 2. Renames /path/to/.target.new to /path/to/target +// 3. If the final rename is successful, deletes /path/to/.target.old, returns no error. On Windows, +// the removal of /path/to/target.old always fails, so instead Apply hides the old file instead. +// 4. If the final rename fails, attempts to roll back by renaming /path/to/.target.old +// back to /path/to/target. // // If the roll back operation fails, the file system is left in an inconsistent state where there is // no new executable file and the old executable file could not be be moved to its original location. @@ -233,12 +233,40 @@ func (o *Options) CheckPermissions() error { return nil } +// normalizeExecutablePath handles the case where os.Executable() returns a path +// ending with .old (which can happen after a previous update on some systems). +// It strips the .old suffix and any leading dot from the base filename. +// +// Examples: +// - /path/to/.minio.old -> /path/to/minio +// - /path/to/program.old -> /path/to/program +// - /path/to/program -> /path/to/program (unchanged) +func normalizeExecutablePath(path string) string { + path = filepath.Clean(path) + baseFilename := filepath.Base(path) + + // Handle case where the executable path ends with .old + if filepath.Ext(baseFilename) == ".old" { + baseFilename = baseFilename[:len(baseFilename)-4] // Remove .old suffix + // Remove leading dot if present (e.g., ".minio.old" -> "minio") + if len(baseFilename) > 1 && baseFilename[0] == '.' { + baseFilename = baseFilename[1:] + } + path = filepath.Join(filepath.Dir(path), baseFilename) + } + + return path +} + func (o *Options) getPath() (string, error) { if o.TargetPath == "" { - return osext.Executable() - } else { - return o.TargetPath, nil + path, err := osext.Executable() + if err != nil { + return "", err + } + return normalizeExecutablePath(path), nil } + return o.TargetPath, nil } func (o *Options) getMode() os.FileMode { diff --git a/apply_test.go b/apply_test.go index fa389fd..52b1f58 100644 --- a/apply_test.go +++ b/apply_test.go @@ -247,3 +247,61 @@ func sign(parsePrivKey func([]byte) (crypto.Signer, error), privatePEM string, s return sig } + +func TestNormalizeExecutablePath(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + { + name: "normal path unchanged", + input: "/path/to/program", + expected: "/path/to/program", + }, + { + name: "path with .old suffix", + input: "/path/to/program.old", + expected: "/path/to/program", + }, + { + name: "path with leading dot and .old suffix", + input: "/path/to/.minio.old", + expected: "/path/to/minio", + }, + { + name: "path with leading dot and .old suffix (complex)", + input: "/usr/local/bin/.server.old", + expected: "/usr/local/bin/server", + }, + { + name: "simple filename with .old", + input: "program.old", + expected: "program", + }, + { + name: "simple filename with leading dot and .old", + input: ".program.old", + expected: "program", + }, + { + name: "path with multiple dots but not .old extension", + input: "/path/to/my.program.exe", + expected: "/path/to/my.program.exe", + }, + { + name: "windows style path with .old", + input: "C:\\Program Files\\app.exe.old", + expected: "C:\\Program Files\\app.exe", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := normalizeExecutablePath(tt.input) + if result != tt.expected { + t.Errorf("normalizeExecutablePath(%q) = %q, expected %q", tt.input, result, tt.expected) + } + }) + } +} From 0039a3f2e6593388f771bcad9bf6b2b246cbb434 Mon Sep 17 00:00:00 2001 From: Allan Roger Reid Date: Tue, 14 Oct 2025 15:55:57 -0700 Subject: [PATCH 2/3] govulncheck@latest (v1.1.4) requires at least Go 1.23 --- .github/workflows/vulncheck.yml | 32 +++++++++++++++++--------------- go.mod | 6 ++++-- go.sum | 6 ------ 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/.github/workflows/vulncheck.yml b/.github/workflows/vulncheck.yml index 7b8dfe8..2165bf2 100644 --- a/.github/workflows/vulncheck.yml +++ b/.github/workflows/vulncheck.yml @@ -12,19 +12,21 @@ jobs: vulncheck: name: Analysis runs-on: ubuntu-latest - strategy: - matrix: - go-version: [ 1.19 ] steps: - - name: Check out code into the Go module directory - uses: actions/checkout@v3 - - uses: actions/setup-go@v3 - with: - go-version: ${{ matrix.go-version }} - check-latest: true - - name: Get govulncheck - run: go install golang.org/x/vuln/cmd/govulncheck@latest - shell: bash - - name: Run govulncheck - run: govulncheck ./... - shell: bash + - name: Check out code into the Go module directory + uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: 1.24.0 # Same as 'go x.y.z' in go.mod + cache: false + - name: Get official govulncheck + shell: bash + env: + TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + run: | + git config --global url."https://minio-bot:${TOKEN}@github.com".insteadOf "https://github.com" + go install golang.org/x/vuln/cmd/govulncheck@latest + - name: Run govulncheck + run: govulncheck -show verbose -scan package ./... + shell: bash diff --git a/go.mod b/go.mod index 767797b..3497899 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,10 @@ module github.com/minio/selfupdate -go 1.14 +go 1.24 + +require aead.dev/minisign v0.2.0 require ( - aead.dev/minisign v0.2.0 golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b // indirect + golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect ) diff --git a/go.sum b/go.sum index cf466e4..62c05d4 100644 --- a/go.sum +++ b/go.sum @@ -5,16 +5,10 @@ golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b h1:QAqMVf3pSa6eeTsuklijukjXBlj7Es2QQplab+/RbQ4= golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From b758e53aa53f5213b3b57e011d129160b3f9d2f2 Mon Sep 17 00:00:00 2001 From: Allan Roger Reid Date: Tue, 14 Oct 2025 17:05:02 -0700 Subject: [PATCH 3/3] govulncheck --- go.mod | 8 +++++--- go.sum | 4 ++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3497899..dc90207 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,12 @@ module github.com/minio/selfupdate -go 1.24 +go 1.24.0 + +toolchain go1.24.8 require aead.dev/minisign v0.2.0 require ( - golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b // indirect - golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect + golang.org/x/crypto v0.43.0 // indirect + golang.org/x/sys v0.37.0 // indirect ) diff --git a/go.sum b/go.sum index 62c05d4..212ba66 100644 --- a/go.sum +++ b/go.sum @@ -4,11 +4,15 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b h1:QAqMVf3pSa6eeTsuklijukjXBlj7Es2QQplab+/RbQ4= golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=