Understanding Permission Denied Errors
Permission denied errors in Go appear as:
open /etc/shadow: permission denied
mkdir /var/log/myapp: permission denied
stat /root/config: permission denied
exec: /path/to/script: permission deniedThe error is of type *os.PathError with Err being os.ErrPermission.
Common Scenarios and Solutions
Scenario 1: Writing to Protected Directory
Problem code:
``go
func main() {
err := os.WriteFile("/etc/myapp/config.json", []byte("{}"), 0644)
if err != nil {
log.Fatal(err) // open /etc/myapp/config.json: permission denied
}
}
Solution - Use appropriate directory: ```go func getConfigPath() string { // Check for custom config path from environment if path := os.Getenv("MYAPP_CONFIG"); path != "" { return path }
// Use user-writable locations home, err := os.UserHomeDir() if err == nil { return filepath.Join(home, ".myapp", "config.json") }
// Fallback to temp directory return filepath.Join(os.TempDir(), "myapp", "config.json") }
func main() { configPath := getConfigPath()
// Create directory if needed dir := filepath.Dir(configPath) if err := os.MkdirAll(dir, 0755); err != nil { log.Fatal(err) }
err := os.WriteFile(configPath, []byte("{}"), 0644) if err != nil { log.Fatal(err) } } ```
Scenario 2: Running as Wrong User
Problem code:
``go
func main() {
// Running as non-root but accessing root-owned file
data, err := os.ReadFile("/var/log/syslog")
if err != nil {
log.Fatal(err) // permission denied
}
}
Solution - Check and escalate privileges: ```go func main() { // Check if running as root if os.Geteuid() != 0 { fmt.Println("This program requires root privileges") os.Exit(1) }
data, err := os.ReadFile("/var/log/syslog") if err != nil { log.Fatal(err) } fmt.Println(string(data)) } ```
Or use sudo for specific operations:
``go
func readFileWithSudo(path string) ([]byte, error) {
cmd := exec.Command("sudo", "cat", path)
return cmd.Output()
}
Scenario 3: File Permission Bits
Problem code: ```go func main() { // Created file with wrong permissions os.WriteFile("secret.txt", []byte("password"), 0777) // Too permissive!
// Now can't read it os.Chmod("secret.txt", 0000) // No permissions at all data, err := os.ReadFile("secret.txt") if err != nil { log.Fatal(err) // permission denied } } ```
Solution - Set correct permissions: ```go func main() { // Create with restricted permissions err := os.WriteFile("secret.txt", []byte("password"), 0600) if err != nil { log.Fatal(err) }
// Fix existing file permissions err = os.Chmod("secret.txt", 0600) // Owner read/write only if err != nil { log.Fatal(err) }
// Or change ownership (requires root) err = os.Chown("secret.txt", os.Getuid(), os.Getgid()) if err != nil { log.Fatal(err) } } ```
Permission reference:
``go
// Common permission modes
const (
DirPerms = 0755 // drwxr-xr-x: dirs with listing
FilePerms = 0644 // -rw-r--r--: files readable by others
SecretPerms = 0600 // -rw-------: sensitive files
ExecPerms = 0755 // -rwxr-xr-x: executable files
)
Scenario 4: Executable Permission
Error:
``
exec: /path/to/script.sh: permission denied
Solution - Set executable bit: ```go func runScript(path string) error { // Make executable if err := os.Chmod(path, 0755); err != nil { return fmt.Errorf("chmod: %w", err) }
// Run the script cmd := exec.Command(path) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd.Run() }
// Or run shell script directly without exec bit func runShellScript(path string) error { cmd := exec.Command("sh", path) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd.Run() } ```
Scenario 5: Directory Traversal Prevention
Problem code:
``go
func main() {
// User input directly used in path
filename := getUserInput()
path := filepath.Join("/var/data", filename)
data, err := os.ReadFile(path)
// If filename is "../../../etc/passwd", reads system file!
}
Solution - Validate and sanitize paths: ```go func safeReadFile(baseDir, filename string) ([]byte, error) { // Clean the filename filename = filepath.Clean(filename)
// Construct full path fullPath := filepath.Join(baseDir, filename)
// Resolve to absolute path absBase, err := filepath.Abs(baseDir) if err != nil { return nil, err }
absPath, err := filepath.Abs(fullPath) if err != nil { return nil, err }
// Verify path is within base directory if !strings.HasPrefix(absPath, absBase+string(filepath.Separator)) { return nil, os.ErrPermission // Prevent directory traversal }
// Check if it's actually a file (not directory) info, err := os.Stat(absPath) if err != nil { return nil, err } if info.IsDir() { return nil, fmt.Errorf("path is a directory, not a file") }
return os.ReadFile(absPath) } ```
Scenario 6: Windows-Specific Permissions
Error on Windows:
``
open C:\Program Files\MyApp\config.txt: Access is denied.
Solution for Windows: ```go func getAppDataPath() string { if runtime.GOOS == "windows" { // Use AppData for user-writable storage appData := os.Getenv("APPDATA") if appData == "" { appData = os.Getenv("LOCALAPPDATA") } return filepath.Join(appData, "MyApp") }
home, _ := os.UserHomeDir() return filepath.Join(home, ".myapp") }
// Windows-specific ACL handling func setWindowsPermissions(path string) error { if runtime.GOOS != "windows" { return nil // Not needed on Unix }
// Use icacls command for Windows ACLs cmd := exec.Command("icacls", path, "/grant", "Users:(R,W)") return cmd.Run() } ```
Robust Permission Handling
```go type FileHandler struct { baseDir string umask os.FileMode }
func NewFileHandler(baseDir string) (*FileHandler, error) { absDir, err := filepath.Abs(baseDir) if err != nil { return nil, err }
// Create base directory with secure permissions if err := os.MkdirAll(absDir, 0755); err != nil { return nil, err }
return &FileHandler{ baseDir: absDir, umask: 0077, // Restrict to owner only }, nil }
func (h *FileHandler) SafeWrite(name string, data []byte) error { path := filepath.Join(h.baseDir, name)
// Validate path absPath, err := filepath.Abs(path) if err != nil { return err } if !strings.HasPrefix(absPath, h.baseDir) { return os.ErrPermission }
// Create parent directory dir := filepath.Dir(absPath) if err := os.MkdirAll(dir, 0755); err != nil { return err }
// Write to temp file first tmpPath := absPath + ".tmp" if err := os.WriteFile(tmpPath, data, 0600); err != nil { return err }
// Atomic rename return os.Rename(tmpPath, absPath) }
func (h *FileHandler) SafeRead(name string) ([]byte, error) { path := filepath.Join(h.baseDir, name)
// Validate path absPath, err := filepath.Abs(path) if err != nil { return nil, err } if !strings.HasPrefix(absPath, h.baseDir) { return nil, os.ErrPermission }
return os.ReadFile(absPath) } ```
Checking Permissions Before Operations
```go func checkWritePermission(path string) error { // Check if directory is writable dir := filepath.Dir(path)
// Create directory if it doesn't exist if _, err := os.Stat(dir); os.IsNotExist(err) { if err := os.MkdirAll(dir, 0755); err != nil { return fmt.Errorf("cannot create directory: %w", err) } }
// Try to create a temp file tmpPath := filepath.Join(dir, ".write_test") f, err := os.Create(tmpPath) if err != nil { return fmt.Errorf("write permission denied: %w", err) } f.Close() os.Remove(tmpPath)
return nil } ```
Verification
```bash # Check file permissions ls -la /path/to/file
# Check directory permissions ls -ld /path/to/directory
# Check current user whoami id
# Check file ownership stat /path/to/file
# Try with elevated privileges sudo ./myapp ```