From f86b3003b1c89871f18cdfb831438222cdbb9bb1 Mon Sep 17 00:00:00 2001 From: Maxim Slipenko Date: Tue, 17 Jun 2025 18:56:19 +0000 Subject: [PATCH] fix: add symlink handling in createFirejailedBinary (#108) closes #107 Reviewed-on: https://gitea.plemya-x.ru/Plemya-x/ALR/pulls/108 Co-authored-by: Maxim Slipenko Co-committed-by: Maxim Slipenko --- internal/build/firejail.go | 94 +++++++++++++++++++++++++- internal/build/firejail_test.go | 14 ++-- internal/translations/default.pot | 2 +- internal/translations/po/ru/default.po | 10 +-- 4 files changed, 107 insertions(+), 13 deletions(-) diff --git a/internal/build/firejail.go b/internal/build/firejail.go index 5c5c766..0956d62 100644 --- a/internal/build/firejail.go +++ b/internal/build/firejail.go @@ -28,7 +28,6 @@ import ( "github.com/goreleaser/nfpm/v2/files" "github.com/leonelquinteros/gotext" - "gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" ) @@ -51,6 +50,92 @@ var binaryDirectories = []string{ "/usr/local/bin/", } +func moveWithSymlinkHandling(src, dst string) error { + srcInfo, err := os.Lstat(src) + if err != nil { + return fmt.Errorf("failed to get source info: %w", err) + } + + if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil { + return fmt.Errorf("failed to create destination directory: %w", err) + } + + if srcInfo.Mode()&os.ModeSymlink != 0 { + return moveSymlink(src, dst) + } + + if err := os.Rename(src, dst); err != nil { + return copyAndRemove(src, dst) + } + + return nil +} + +func moveSymlink(src, dst string) error { + target, err := os.Readlink(src) + if err != nil { + return fmt.Errorf("failed to read symlink: %w", err) + } + + if err := os.Symlink(target, dst); err != nil { + return fmt.Errorf("failed to create symlink: %w", err) + } + + if err := os.Remove(src); err != nil { + os.Remove(dst) + return fmt.Errorf("failed to remove original symlink: %w", err) + } + + return nil +} + +func copyAndRemove(src, dst string) error { + srcFile, err := os.Open(src) + if err != nil { + return fmt.Errorf("failed to open source: %w", err) + } + defer srcFile.Close() + + dstFile, err := os.Create(dst) + if err != nil { + return fmt.Errorf("failed to create destination: %w", err) + } + defer dstFile.Close() + + if _, err := io.Copy(dstFile, srcFile); err != nil { + return fmt.Errorf("failed to copy content: %w", err) + } + + srcInfo, err := srcFile.Stat() + if err != nil { + return fmt.Errorf("failed to get source stats: %w", err) + } + + if err := dstFile.Chmod(srcInfo.Mode()); err != nil { + return fmt.Errorf("failed to set permissions: %w", err) + } + + if err := os.Remove(src); err != nil { + return fmt.Errorf("failed to remove source: %w", err) + } + + return nil +} + +func moveFileWithErrorHandling(src, dst string) error { + err := moveWithSymlinkHandling(src, dst) + if err != nil { + if os.IsPermission(err) { + return fmt.Errorf("permission denied: %w", err) + } + if os.IsNotExist(err) { + return fmt.Errorf("source file does not exist: %w", err) + } + return fmt.Errorf("failed to move file: %w", err) + } + return nil +} + func applyFirejailIntegration( vars *alrsh.Package, dirs types.Directories, @@ -143,12 +228,15 @@ func createFirejailedBinary( return nil, err } - if err := osutils.Move(content.Source, filepath.Join(dirs.PkgDir, origFilePath)); err != nil { + if err := moveFileWithErrorHandling(filepath.Join(dirs.PkgDir, content.Destination), filepath.Join(dirs.PkgDir, origFilePath)); err != nil { return nil, fmt.Errorf("failed to move original binary: %w", err) } + content.Type = "file" + content.Source = filepath.Join(dirs.PkgDir, content.Destination) + // Create wrapper script - if err := createWrapperScript(content.Source, origFilePath, dest); err != nil { + if err := createWrapperScript(filepath.Join(dirs.PkgDir, content.Destination), origFilePath, dest); err != nil { return nil, fmt.Errorf("failed to create wrapper script: %w", err) } diff --git a/internal/build/firejail_test.go b/internal/build/firejail_test.go index dcc6bcd..c253c07 100644 --- a/internal/build/firejail_test.go +++ b/internal/build/firejail_test.go @@ -138,7 +138,10 @@ func TestCreateFirejailedBinary(t *testing.T) { os.MkdirAll(pkgDir, 0o755) os.MkdirAll(scriptDir, 0o755) - srcBinary := filepath.Join(tmpDir, "test-binary") + binDir := filepath.Join(pkgDir, "usr", "bin") + os.MkdirAll(binDir, 0o755) + + srcBinary := filepath.Join(binDir, "test-binary") os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755) defaultProfile := filepath.Join(scriptDir, "default.profile") @@ -154,7 +157,7 @@ func TestCreateFirejailedBinary(t *testing.T) { content := &files.Content{ Source: srcBinary, - Destination: "./usr/bin/test-binary", + Destination: "/usr/bin/test-binary", Type: "file", } @@ -172,7 +175,10 @@ func TestCreateFirejailedBinary(t *testing.T) { os.MkdirAll(pkgDir, 0o755) os.MkdirAll(scriptDir, 0o755) - srcBinary := filepath.Join(tmpDir, "special-binary") + binDir := filepath.Join(pkgDir, "usr", "bin") + os.MkdirAll(binDir, 0o755) + + srcBinary := filepath.Join(binDir, "special-binary") os.WriteFile(srcBinary, []byte("#!/bin/bash\necho special"), 0o755) defaultProfile := filepath.Join(scriptDir, "default.profile") @@ -191,7 +197,7 @@ func TestCreateFirejailedBinary(t *testing.T) { content := &files.Content{ Source: srcBinary, - Destination: "./usr/bin/special-binary", + Destination: "/usr/bin/special-binary", Type: "file", } diff --git a/internal/translations/default.pot b/internal/translations/default.pot index 101951e..1fe7729 100644 --- a/internal/translations/default.pot +++ b/internal/translations/default.pot @@ -220,7 +220,7 @@ msgstr "" msgid "AutoReq is not implemented for this package format, so it's skipped" msgstr "" -#: internal/build/firejail.go:59 +#: internal/build/firejail.go:144 msgid "Applying FireJail integration" msgstr "" diff --git a/internal/translations/po/ru/default.po b/internal/translations/po/ru/default.po index 4be205f..f473162 100644 --- a/internal/translations/po/ru/default.po +++ b/internal/translations/po/ru/default.po @@ -12,8 +12,8 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " -"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "X-Generator: Gtranslator 48.0\n" #: build.go:42 @@ -231,7 +231,7 @@ msgid "AutoReq is not implemented for this package format, so it's skipped" msgstr "" "AutoReq не реализовано для этого формата пакета, поэтому будет пропущено" -#: internal/build/firejail.go:59 +#: internal/build/firejail.go:144 msgid "Applying FireJail integration" msgstr "Применение интеграции FireJail" @@ -356,8 +356,8 @@ msgid "" "This command is deprecated and would be removed in the future, use \"%s\" " "instead!" msgstr "" -"Эта команда устарела и будет удалена в будущем, используйте вместо нее " -"\"%s\"!" +"Эта команда устарела и будет удалена в будущем, используйте вместо нее \"%s" +"\"!" #: internal/db/db.go:76 msgid "Database version mismatch; resetting"