Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for privilege escalation vulnerability #6748

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
83 changes: 83 additions & 0 deletions internal/aghos/binarysecurity_others.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//go:build !windows

package aghos

import (
"fmt"
"os"
"strings"

"github.com/AdguardTeam/golibs/errors"
)

// protectedDirectories are directories which contain other application binaries,
// as such AdGuard Home should never attempt store application data here, at risk of
// overwriting other files. Moreover, these directories are innapproriate for storage of
// config files or session storage.
var protectedDirectories = []string{
"/usr/bin",
"/usr/sbin",
"/user/bin",
}

// serviceInstallDir is a executable path in a directory which secure permissions
// which prevent the manipulation of the binary.
const serviceInstallDir = "/usr/bin/AdGuardHome"

// SecureBinary is used before service.Install(). This function protects AdGuardHome from
// privilege escalation vulnerabilities caused by writable files
func SecureBinary() error {
// Installalation can only be completed with root privileges, so check and handle if not
if os.Getuid() != 0 {
return errors.Error("permission denied. Root privileges required")
}

// Get current file path
binary, err := os.Executable()
if err != nil {
return fmt.Errorf("os.Executable(): %w", err)
}

// Change owner to root:root
err = os.Chown(binary, 0, 0)
if err != nil {
return fmt.Errorf("os.Chown() %q: %w", binary, err)
}

// Set permissions to root(read,write,exec), group(read,exec), public(read)
// This combined with changing the owner make the file undeletable without root privlages
// UNLESS THE PARENT FOLDER IS WRITABLE!
err = os.Chmod(binary, 0755)
if err != nil {
return fmt.Errorf("os.Chmod() %q: %w", binary, err)
}

// Move binary to the PATH in a folder which is read-only to non root users
// If already moved, this is a no-op
err = os.Rename(binary, serviceInstallDir)
if err != nil {
return fmt.Errorf("os.Rename() %q to %q: %w", binary, serviceInstallDir, err)
}

return nil
}

// CurrentDirAvaliable returns true if it is okay to use this directory to store application
// data.
func CurrentDirAvaliable() (bool, error) {
binary, err := os.Executable()
if err != nil {
return false, fmt.Errorf("os.Executable(): %w", err)
}

for i := 0; i < len(protectedDirectories); i++ {
// Check if binary is within a protected directory
if strings.HasPrefix(binary, protectedDirectories[i]) {
// The binary is within a protected directory
return false, nil
}
}

// The binary is outside of all checked protected directories
return true, nil
}
47 changes: 47 additions & 0 deletions internal/aghos/binarysecurity_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//go:build windows

package aghos

import (
"fmt"
"os"
"strings"
)

// securePrefixDirectories is a list of directories where a service binary
// has the appropriate permissions to mitigate a binary planting attack
var securePrefixDirectories = []string{
"C:\\Program Files",
"C:\\Program Files (x86)",

// Some Windows users place binaries within /Windows/System32 to add it to %PATH%
"C:\\Windows",
}

// SecureBinary is used before service.Install(). This function protects AdGuardHome from
// privilege escalation vulnerabilities caused by writable files
func SecureBinary() error {
// Get current file path
binary, err := os.Executable()
if err != nil {
return fmt.Errorf("os.Executable(): %w", err)
}

for i := 0; i < len(securePrefixDirectories); i++ {
// Check if binary is within a secure folder write protected folder
if strings.HasPrefix(binary, securePrefixDirectories[i]) {
// The binary is within a secure directory already
return nil
}
}

// No secure directories matched
return fmt.Errorf("insecure binary location for service instalation: %q. Please view: https://adguard-dns.io/kb/adguard-home/running-securely/", binary)
}

// CurrentDirAvaliable returns true if it is okay to use this directory to store application
// data.
func CurrentDirAvaliable() (bool, error) {
// We do not mind what directory is used on Windows
return true, nil
}
16 changes: 16 additions & 0 deletions internal/home/home.go
Original file line number Diff line number Diff line change
Expand Up @@ -760,9 +760,25 @@ func initWorkingDir(opts options) (err error) {
return err
}

// Can we use the current directory to store application data?
pwdAvaliable, err := aghos.CurrentDirAvaliable()
if err != nil {
return err
}

if opts.workDir != "" {
// If there is a custom config file, use it's directory as our working dir
Context.workDir = opts.workDir
} else if !pwdAvaliable {
// If running as a service and from /usr/bin/ use /var/lib for working dir instead of
// /usr/bin/data
Context.workDir = "/var/lib/AdGuardHome"

// Create dir if it does not already exist
err := os.MkdirAll(Context.workDir, 0755)
if err != nil {
return fmt.Errorf("os.MkdirAll: %s: %w", Context.workDir, err)
}
} else {
Context.workDir = filepath.Dir(execPath)
}
Expand Down
5 changes: 5 additions & 0 deletions internal/home/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,11 @@ func handleServiceStatusCommand(s service.Service) {

// handleServiceStatusCommand handles service "install" command
func handleServiceInstallCommand(s service.Service) {
// Set the binary's permissions and move to /usr/bin (if on linux)
go-compile marked this conversation as resolved.
Show resolved Hide resolved
if err := aghos.SecureBinary(); err != nil {
log.Fatal(err)
}

err := svcAction(s, "install")
if err != nil {
log.Fatalf("service: executing action %q: %s", "install", err)
Expand Down