fix: add symlink handling in createFirejailedBinary (#108)
All checks were successful
Update alr-git / changelog (push) Successful in 30s

closes #107

Reviewed-on: #108
Co-authored-by: Maxim Slipenko <no-reply@maxim.slipenko.com>
Co-committed-by: Maxim Slipenko <no-reply@maxim.slipenko.com>
This commit is contained in:
2025-06-17 18:56:19 +00:00
committed by Maxim Slipenko
parent bd79dcf401
commit f86b3003b1
4 changed files with 107 additions and 13 deletions

View File

@ -28,7 +28,6 @@ import (
"github.com/goreleaser/nfpm/v2/files" "github.com/goreleaser/nfpm/v2/files"
"github.com/leonelquinteros/gotext" "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/alrsh"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
) )
@ -51,6 +50,92 @@ var binaryDirectories = []string{
"/usr/local/bin/", "/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( func applyFirejailIntegration(
vars *alrsh.Package, vars *alrsh.Package,
dirs types.Directories, dirs types.Directories,
@ -143,12 +228,15 @@ func createFirejailedBinary(
return nil, err 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) 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 // 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) return nil, fmt.Errorf("failed to create wrapper script: %w", err)
} }

View File

@ -138,7 +138,10 @@ func TestCreateFirejailedBinary(t *testing.T) {
os.MkdirAll(pkgDir, 0o755) os.MkdirAll(pkgDir, 0o755)
os.MkdirAll(scriptDir, 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) os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755)
defaultProfile := filepath.Join(scriptDir, "default.profile") defaultProfile := filepath.Join(scriptDir, "default.profile")
@ -154,7 +157,7 @@ func TestCreateFirejailedBinary(t *testing.T) {
content := &files.Content{ content := &files.Content{
Source: srcBinary, Source: srcBinary,
Destination: "./usr/bin/test-binary", Destination: "/usr/bin/test-binary",
Type: "file", Type: "file",
} }
@ -172,7 +175,10 @@ func TestCreateFirejailedBinary(t *testing.T) {
os.MkdirAll(pkgDir, 0o755) os.MkdirAll(pkgDir, 0o755)
os.MkdirAll(scriptDir, 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) os.WriteFile(srcBinary, []byte("#!/bin/bash\necho special"), 0o755)
defaultProfile := filepath.Join(scriptDir, "default.profile") defaultProfile := filepath.Join(scriptDir, "default.profile")
@ -191,7 +197,7 @@ func TestCreateFirejailedBinary(t *testing.T) {
content := &files.Content{ content := &files.Content{
Source: srcBinary, Source: srcBinary,
Destination: "./usr/bin/special-binary", Destination: "/usr/bin/special-binary",
Type: "file", Type: "file",
} }

View File

@ -220,7 +220,7 @@ msgstr ""
msgid "AutoReq is not implemented for this package format, so it's skipped" msgid "AutoReq is not implemented for this package format, so it's skipped"
msgstr "" msgstr ""
#: internal/build/firejail.go:59 #: internal/build/firejail.go:144
msgid "Applying FireJail integration" msgid "Applying FireJail integration"
msgstr "" msgstr ""

View File

@ -12,8 +12,8 @@ msgstr ""
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Generator: Gtranslator 48.0\n" "X-Generator: Gtranslator 48.0\n"
#: build.go:42 #: build.go:42
@ -231,7 +231,7 @@ msgid "AutoReq is not implemented for this package format, so it's skipped"
msgstr "" msgstr ""
"AutoReq не реализовано для этого формата пакета, поэтому будет пропущено" "AutoReq не реализовано для этого формата пакета, поэтому будет пропущено"
#: internal/build/firejail.go:59 #: internal/build/firejail.go:144
msgid "Applying FireJail integration" msgid "Applying FireJail integration"
msgstr "Применение интеграции FireJail" msgstr "Применение интеграции FireJail"
@ -356,8 +356,8 @@ msgid ""
"This command is deprecated and would be removed in the future, use \"%s\" " "This command is deprecated and would be removed in the future, use \"%s\" "
"instead!" "instead!"
msgstr "" msgstr ""
"Эта команда устарела и будет удалена в будущем, используйте вместо нее " "Эта команда устарела и будет удалена в будущем, используйте вместо нее \"%s"
"\"%s\"!" "\"!"
#: internal/db/db.go:76 #: internal/db/db.go:76
msgid "Database version mismatch; resetting" msgid "Database version mismatch; resetting"