Skip to content

Instantly share code, notes, and snippets.

@praveenkumar
Created June 29, 2026 05:19
Show Gist options
  • Select an option

  • Save praveenkumar/707930aa9006c1bf30c7d0506d663a1f to your computer and use it in GitHub Desktop.

Select an option

Save praveenkumar/707930aa9006c1bf30c7d0506d663a1f to your computer and use it in GitHub Desktop.
Drop privileges to the real user
//go:build darwin || linux
// +build darwin linux
package main
import (
"bufio"
"fmt"
"io"
"os"
"syscall"
)
func main() {
if err := run(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
func run() error {
// Get the real user ID (the user who invoked sudo/the setuid binary)
// When running as setuid root, getuid() returns 0 (root)
// but we want the original user who invoked the command
realUID := syscall.Getuid()
realGID := syscall.Getgid()
// On macOS/Linux, if this is a setuid binary, we need to get the
// effective UID before it was elevated
// SUDO_UID and SUDO_GID env vars are set by sudo
if sudoUID := os.Getenv("SUDO_UID"); sudoUID != "" {
fmt.Printf("Running via sudo, original user UID from SUDO_UID: %s\n", sudoUID)
}
euid := syscall.Geteuid()
egid := syscall.Getegid()
fmt.Printf("Real UID: %d, Effective UID: %d\n", realUID, euid)
fmt.Printf("Real GID: %d, Effective GID: %d\n", realGID, egid)
// Step 1: Open /etc/hosts with read/write permissions while privileged
fmt.Println("\n[Step 1] Opening /etc/hosts while privileged...")
hostsFile, err := os.OpenFile("/etc/hosts", os.O_RDWR, 0644)
if err != nil {
return fmt.Errorf("failed to open /etc/hosts (need root): %w", err)
}
defer hostsFile.Close()
// Step 2: Drop privileges to the real user
// IMPORTANT: Drop supplementary groups first, then GID, then UID
fmt.Println("\n[Step 2] Dropping privileges...")
// For a setuid binary, we want to drop to the real user
targetUID := realUID
targetGID := realGID
// If running via sudo, the real UID will be 0, so we can't use it
// In production, you'd parse SUDO_UID/SUDO_GID
if targetUID == 0 {
fmt.Println("Warning: Real UID is 0 (root). In production, parse SUDO_UID.")
fmt.Println("Skipping privilege drop to avoid becoming nobody...")
// Don't drop privileges in this demo if we can't determine the target user
} else {
// Drop supplementary groups
if err := syscall.Setgroups([]int{}); err != nil {
return fmt.Errorf("failed to drop supplementary groups: %w", err)
}
// Drop GID first (must be done before dropping UID on some systems)
if err := syscall.Setgid(targetGID); err != nil {
return fmt.Errorf("failed to setgid(%d): %w", targetGID, err)
}
// Drop UID last
if err := syscall.Setuid(targetUID); err != nil {
return fmt.Errorf("failed to setuid(%d): %w", targetUID, err)
}
// Verify we can't get privileges back
if err := syscall.Setuid(0); err == nil {
return fmt.Errorf("SECURITY ERROR: able to regain root after dropping privileges!")
}
fmt.Printf("Successfully dropped to UID %d, GID %d\n", targetUID, targetGID)
}
// Verify current privileges
newUID := syscall.Getuid()
newEUID := syscall.Geteuid()
fmt.Printf("New Real UID: %d, New Effective UID: %d\n", newUID, newEUID)
// Step 3: Read and parse /etc/hosts using the pre-opened file descriptor
fmt.Println("\n[Step 3] Reading /etc/hosts using pre-opened FD...")
if err := readHostsFile(hostsFile); err != nil {
return fmt.Errorf("failed to read hosts: %w", err)
}
// Step 4: Write to /etc/hosts using the pre-opened file descriptor
fmt.Println("\n[Step 4] Writing to /etc/hosts using pre-opened FD...")
testEntry := "# Test entry added by privilege-drop demo\n"
if err := appendToHosts(hostsFile, testEntry); err != nil {
return fmt.Errorf("failed to write to hosts: %w", err)
}
fmt.Println("Successfully wrote to /etc/hosts after dropping privileges!")
// Step 5: Verify we can't open /etc/hosts anymore (don't have permissions)
fmt.Println("\n[Step 6] Verifying we can't open new privileged files...")
if _, err := os.OpenFile("/etc/hosts", os.O_RDWR, 0644); err != nil {
fmt.Printf("Good: Can't open /etc/hosts anymore: %v\n", err)
} else {
fmt.Println("WARNING: Still able to open /etc/hosts!")
}
return nil
}
func readHostsFile(f *os.File) error {
// Seek to beginning
if _, err := f.Seek(0, io.SeekStart); err != nil {
return err
}
scanner := bufio.NewScanner(f)
lineCount := 0
for scanner.Scan() {
lineCount++
if lineCount <= 5 {
fmt.Printf(" Line %d: %s\n", lineCount, scanner.Text())
}
}
fmt.Printf(" ... (read %d total lines)\n", lineCount)
return scanner.Err()
}
func appendToHosts(f *os.File, entry string) error {
// Seek to end
if _, err := f.Seek(0, io.SeekEnd); err != nil {
return err
}
// Write the entry
if _, err := f.WriteString(entry); err != nil {
return err
}
// Sync to disk
return f.Sync()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment