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 file permissions on windows #1758

Merged
merged 4 commits into from
Nov 26, 2024
Merged

Fix file permissions on windows #1758

merged 4 commits into from
Nov 26, 2024

Conversation

vlabo
Copy link
Member

@vlabo vlabo commented Nov 26, 2024

Summary by CodeRabbit

  • New Features

    • Enhanced file permission handling for Windows systems across various functionalities.
  • Bug Fixes

    • Improved error handling and logging for permission changes, providing clearer context for failures.
  • Chores

    • Updated dependencies to include github.com/hectane/go-acl for better cross-platform support.

@vlabo vlabo requested a review from dhaavi November 26, 2024 08:15
Copy link
Contributor

coderabbitai bot commented Nov 26, 2024

📝 Walkthrough
📝 Walkthrough

Walkthrough

The changes in this pull request primarily focus on enhancing file permission handling across different operating systems, particularly for Windows. The github.com/hectane/go-acl package is introduced to manage access control lists, allowing for proper permission settings on Windows. Multiple files are updated to check the operating system and adjust permission-setting logic accordingly, while also improving error handling and logging. The modifications aim to ensure consistent behavior across platforms while maintaining the existing functionality for non-Windows systems.

Changes

File Change Summary
base/database/storage/fstree/fstree.go Modified writeFile function to use acl.Chmod for Windows permission handling; streamlined error handling.
base/updater/fetch.go Updated fetchFile method to set permissions using acl.Chmod on Windows; inverted conditional logic for permission settings.
base/utils/fs.go Enhanced EnsureDirectory function to use acl.Chmod for Windows permission handling; removed placeholder comment regarding permissions.
base/utils/renameio/writefile.go Altered WriteFile function to check OS and use acl.Chmod for Windows; maintained existing behavior for other systems.
cmds/portmaster-start/run.go Modified fixExecPerm function to use acl.Chmod for Windows; refined error handling and adjusted logging setup.
go.mod Added dependency for github.com/hectane/go-acl; retained replace directive for github.com/tc-hib/winres.
service/updates/helper/electron.go Updated EnsureChromeSandboxPermissions function to check OS for SUID permissions; added debug log for permission adjustments.
service/updates/upgrader.go Modified upgradeBinary function to set permissions using acl.Chmod for Windows; adjusted error handling for permission setting.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant System
    participant ACL

    User->>System: Request file operation
    System->>System: Check OS
    alt Windows
        System->>ACL: Set permissions using acl.Chmod
        ACL-->>System: Return success/failure
        System->>User: Notify result
    else Non-Windows
        System->>System: Set permissions using os.Chmod
        System-->>User: Notify result
    end
Loading

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Outside diff range and nitpick comments (9)
base/utils/fs.go (1)

Line range hint 16-50: Consider architectural improvements

  1. Consider wrapping errors with more context about the permission operations:
 if isWindows {
-  return acl.Chmod(path, perm)
+  if err := acl.Chmod(path, perm); err != nil {
+    return fmt.Errorf("failed to set Windows ACL permissions on %s: %w", path, err)
+  }
+  return nil
 }
  1. Consider splitting the implementation into platform-specific files (e.g., fs_windows.go and fs_unix.go) for better maintainability and clearer separation of concerns.
service/updates/helper/electron.go (1)

55-59: Improve error handling for cross-platform support

The error message in suidBitWarning is Linux-specific, mentioning kernel configurations and SUID bits. If Windows support is truly needed (which it shouldn't be, given earlier comments), the error messages should be OS-specific.

Consider using OS-specific error messages:

var (
    linuxSuidBitWarning = `Failed to set SUID permissions for chrome-sandbox. This is required for Linux kernel versions that do not have unprivileged user namespaces (CONFIG_USER_NS_UNPRIVILEGED) enabled. If you're running and up-to-date distribution kernel you can likely ignore this warning. If you encounter issue starting the user interface please either update your kernel or set the SUID bit (mode 0%0o) on %s`
    windowsPermissionWarning = `Failed to set permissions for chrome-sandbox. This might affect the application's functionality. Path: %s`
)
base/utils/atomic.go (2)

9-12: Fix import ordering using gci.

The imports can be automatically formatted using the gci tool.

import (
	"errors"
	"fmt"
	"io"
	"io/fs"
	"os"
	"path"
	"runtime"

-	"github.com/hectane/go-acl"
	"github.com/safing/portmaster/base/utils/renameio"
+	"github.com/hectane/go-acl"
)
🧰 Tools
🪛 golangci-lint (1.62.2)

12-12: File is not gci-ed with --skip-generated -s standard -s default

(gci)

🪛 GitHub Check: Linter

[failure] 12-12:
File is not gci-ed with --skip-generated -s standard -s default (gci)


46-51: Consider adding error context for Windows-specific failures.

When the ACL operation fails on Windows, it would be helpful to include more context about what went wrong.

 	if opts.Mode != 0 {
 		if runtime.GOOS == "windows" {
-			err = acl.Chmod(path.Join(opts.TempDir, dest), opts.Mode)
+			if err := acl.Chmod(tmpFile.Name(), opts.Mode); err != nil {
+				return fmt.Errorf("failed to set Windows ACL permissions on %q: %w", tmpFile.Name(), err)
+			}
 		} else {
-			err = tmpFile.Chmod(opts.Mode)
+			if err := tmpFile.Chmod(opts.Mode); err != nil {
+				return fmt.Errorf("failed to set file permissions on %q: %w", tmpFile.Name(), err)
+			}
 		}
-		if err != nil {
-			return fmt.Errorf("failed to update mode bits of temp file: %w", err)
-		}
 	}
go.mod (1)

Remove outdated TODO comment and replace directive

The TODO comment and replace directive can be removed since:

  1. PR Special handling for API access #4 in tc-hib/winres is closed
  2. The repository is using winres v0.3.1 directly (see dependencies), not the replaced version
  • Remove lines 4-5:
// TODO: Remove when https://github.com/tc-hib/winres/pull/4 is released.
replace github.com/tc-hib/winres => github.com/dhaavi/winres v0.2.2
🔗 Analysis chain

Line range hint 3-4: Consider updating the TODO comment

The TODO comment about removing the replace directive when PR #4 is released seems outdated. Consider either:

  1. Updating the comment with current status
  2. Removing it if the PR has been released
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the referenced PR is still open
gh api repos/tc-hib/winres/pulls/4 --jq '.state'

Length of output: 55


Script:

#!/bin/bash
# Check the content of go.mod to see if there's a TODO comment and any replace directive
cat go.mod

Length of output: 5296

base/database/storage/fstree/fstree.go (2)

18-18: LGTM! Minor import ordering suggestion.

The addition of the go-acl package is appropriate for handling Windows-specific file permissions.

Consider running gci to properly order the imports according to the project's standards:

gci write --skip-generated -s standard -s default base/database/storage/fstree/fstree.go
🧰 Tools
🪛 GitHub Check: Linter

[failure] 18-18:
File is not gci-ed with --skip-generated -s standard -s default (gci)


289-290: Document the Windows-specific behavior

Since the TODOs indicate this is a permanent solution (wont-fix), consider adding a comment explaining the Windows-specific behavior and limitations for future maintainers.

 // writeFile mirrors os.WriteFile, replacing an existing file with the same
-// name atomically. This is not atomic on Windows, but still an improvement.
+// name atomically. This is not atomic on Windows, but provides improved reliability
+// through separate permission setting and atomic replacement operations.
 // TODO: Replace with github.com/google/renamio.WriteFile as soon as it is fixed on Windows.
 // TODO: This has become a wont-fix. Explore other options.
service/updates/upgrader.go (1)

353-358: LGTM: Proper Windows-specific permission handling

The implementation correctly uses platform-specific APIs (acl.Chmod for Windows, os.Chmod for others) while maintaining consistent permissions (0o0755) and error handling.

Consider setting the permissions atomically during the file copy operation to eliminate the window where the file might have incorrect permissions. This could be achieved by modifying the renameio package to support setting permissions during the atomic replace operation.

Example approach:

// In renameio package
type AtomicFile struct {
    *os.File
    permissions os.FileMode
}

func (t *AtomicFile) CloseAtomicallyReplaceWithPerms(perms os.FileMode) error {
    // Set permissions before the atomic rename
    if err := t.Chmod(perms); err != nil {
        return err
    }
    return t.CloseAtomicallyReplace()
}
base/updater/fetch.go (1)

140-145: Consider permission refinements and constants

Two suggestions for improvement:

  1. Consider implementing the TODO comment by differentiating between executable and non-executable files
  2. Consider extracting the permission value (0o0755) into a named constant for better maintainability

Example implementation:

+ const (
+    executableFileMode = 0o0755
+    regularFileMode    = 0o0644
+ )

  if onWindows {
-   err = acl.Chmod(rv.storagePath(), 0o0755)
+   mode := regularFileMode
+   if isExecutable(rv.storagePath()) {
+     mode = executableFileMode
+   }
+   err = acl.Chmod(rv.storagePath(), mode)
    if err != nil {
      log.Warningf("%s: failed to set permissions on downloaded file %s: %s", reg.Name, rv.storagePath(), err)
    }
  } else {
-   // TODO: only set executable files to 0755, set other to 0644
-   err = os.Chmod(rv.storagePath(), 0o0755) //nolint:gosec // See TODO above.
+   mode := regularFileMode
+   if isExecutable(rv.storagePath()) {
+     mode = executableFileMode
+   }
+   err = os.Chmod(rv.storagePath(), mode)
    if err != nil {
      log.Warningf("%s: failed to set permissions on downloaded file %s: %s", reg.Name, rv.storagePath(), err)
    }
  }

Would you like me to provide an implementation of the isExecutable helper function?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between fe070b4 and 1cf591a.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (9)
  • base/database/storage/fstree/fstree.go (2 hunks)
  • base/updater/fetch.go (2 hunks)
  • base/utils/atomic.go (2 hunks)
  • base/utils/fs.go (3 hunks)
  • base/utils/renameio/writefile.go (2 hunks)
  • cmds/portmaster-start/run.go (2 hunks)
  • go.mod (1 hunks)
  • service/updates/helper/electron.go (2 hunks)
  • service/updates/upgrader.go (2 hunks)
🧰 Additional context used
🪛 GitHub Check: Linter
base/database/storage/fstree/fstree.go

[failure] 18-18:
File is not gci-ed with --skip-generated -s standard -s default (gci)

base/updater/fetch.go

[failure] 17-17:
File is not gci-ed with --skip-generated -s standard -s default (gci)

base/utils/atomic.go

[failure] 12-12:
File is not gci-ed with --skip-generated -s standard -s default (gci)

🪛 golangci-lint (1.62.2)
base/updater/fetch.go

17-17: File is not gci-ed with --skip-generated -s standard -s default

(gci)

base/utils/atomic.go

12-12: File is not gci-ed with --skip-generated -s standard -s default

(gci)

🔇 Additional comments (14)
base/utils/renameio/writefile.go (2)

3-8: LGTM: Import changes are appropriate

The addition of runtime and go-acl packages is necessary for cross-platform permission handling.


22-27: Verify Windows ACL behavior and add documentation

While the implementation looks correct, consider:

  1. Adding a comment explaining why different permission handling is needed for Windows
  2. Verifying that acl.Chmod provides equivalent permission handling to t.Chmod

Let's verify the ACL package's usage in the codebase:

Consider adding this documentation:

 // Set permissions before writing data, in case the data is sensitive.
+// Windows requires special handling through the ACL package to properly
+// set file permissions, while other operating systems can use standard chmod.
 if runtime.GOOS == "windows" {
✅ Verification successful

Windows ACL handling is consistently implemented across the codebase

The verification shows that:

  • The ACL package is used consistently throughout the codebase for Windows-specific permission handling
  • The pattern of using acl.Chmod for Windows and standard Chmod for other platforms is uniformly applied
  • Error handling is consistent across all implementations
  • The permissions are properly set with equivalent behavior across platforms (same permission modes used)

The implementation in writefile.go follows the established pattern in the codebase and is correct.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for consistent ACL handling across the codebase
# and verify error handling patterns

# Check for other ACL usage patterns
rg -A 3 "acl\.Chmod" 

# Verify error handling patterns
ast-grep --pattern 'if runtime.GOOS == "windows" {
  $$$
  err = acl.Chmod($_, $_)
  $$$
}'

Length of output: 3230

base/utils/fs.go (2)

9-10: LGTM: Appropriate package for Windows ACL management

The addition of the go-acl package is the correct approach for handling Windows-specific file permissions.


Line range hint 16-50: Verify Windows-specific permission handling

Please ensure that these changes have been tested on Windows with:

  1. Different permission combinations
  2. Special Windows-specific scenarios (e.g., inherited permissions)
  3. Edge cases like system directories or read-only locations
service/updates/helper/electron.go (2)

11-11: LGTM: Appropriate package for Windows ACL handling

The addition of the go-acl package is appropriate for handling file permissions on Windows systems.


Line range hint 1-63: Verify the scope of Windows permission changes

Let's verify if this is part of a larger pattern of Windows permission fixes and if the changes are consistent across the codebase.

✅ Verification successful

Let me gather more context about the Windows-specific permission handling by checking the implementation in other files.


Let me check one more aspect to understand the complete Windows permission handling pattern across the codebase.


Windows permission handling is consistently implemented

The codebase shows a consistent pattern of Windows-specific permission handling:

  • The go-acl package is used systematically across the codebase for Windows-specific permission management
  • The EnsureChromeSandboxPermissions function follows the established pattern of using platform-specific code paths
  • The implementation is isolated to the electron sandbox requirements and doesn't indicate any missing permission fixes elsewhere
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for related Windows permission handling changes

# Check for other files using go-acl
echo "Files using go-acl:"
rg "github.com/hectane/go-acl"

# Check for other Windows-specific permission handling
echo -e "\nOther Windows-specific permission handling:"
rg "runtime.GOOS.*windows.*Chmod"

# Check for related function calls
echo -e "\nUsage of EnsureChromeSandboxPermissions:"
rg "EnsureChromeSandboxPermissions"

Length of output: 1544


Script:

#!/bin/bash
# Check the implementation of Windows-specific permission handling in other files

# Check base/utils/atomic.go implementation
echo "=== atomic.go implementation ==="
ast-grep --pattern 'func $$$Chmod($$$) {
  $$$
}'  base/utils/atomic.go

# Check base/utils/fs.go implementation
echo -e "\n=== fs.go implementation ==="
ast-grep --pattern 'func $$$Chmod($$$) {
  $$$
}'  base/utils/fs.go

# Check base/updater/fetch.go implementation
echo -e "\n=== fetch.go implementation ==="
rg -A 10 "go-acl" base/updater/fetch.go

# Check service/updates/upgrader.go for context
echo -e "\n=== upgrader.go context ==="
rg -A 5 "EnsureChromeSandboxPermissions" service/updates/upgrader.go

Length of output: 1125


Script:

#!/bin/bash
# Check for Windows-specific permission handling patterns

# Check for Windows-specific Chmod calls
echo "=== Windows-specific Chmod patterns ==="
rg -A 3 "if runtime.GOOS == \"windows\".*Chmod"

# Check renameio implementation as it's used in multiple places
echo -e "\n=== renameio implementation ==="
rg -A 5 "Chmod.*windows" base/utils/renameio/

# Check database storage implementation
echo -e "\n=== Database storage implementation ==="
rg -A 5 "acl.*Chmod" base/database/storage/

Length of output: 737

go.mod (1)

35-35: Verify the version of go-acl package

The added dependency github.com/hectane/go-acl is using a dated version from January 2023 (v0.0.0-20230122075934-ca0b05cb1adb). Since this package is critical for Windows permission handling:

  1. Check if there's a newer stable version available
  2. Consider the API stability implications of using a v0.x.x version
✅ Verification successful

Consider upgrading to a newer commit of go-acl package

The current version (v0.0.0-20230122075934-ca0b05cb1adb) is not the latest. Recent commits show important fixes:

While there are open issues, they are either feature requests or dependency updates, not critical bugs. The package remains actively maintained with recent commits in 2024.

Recommendation:

  • Update to the latest commit to benefit from the error handling improvements
  • The v0.x.x version is acceptable as the package is stable despite the version number, evidenced by its long history (since 2017) and focused scope
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check the latest version and commit history of the go-acl package
gh api repos/hectane/go-acl/commits?per_page=5 --jq '.[].commit.message'

# Check for any reported issues or security concerns
gh api repos/hectane/go-acl/issues?state=all --jq '.[] | {title: .title, state: .state, created_at: .created_at}'

Length of output: 3016

base/database/storage/fstree/fstree.go (1)

292-298: ⚠️ Potential issue

Critical: Fix permission setting logic for Windows

The current implementation has a potential issue when setting permissions on Windows:

  • It attempts to set permissions on the target file (filename) which might not exist yet
  • This differs from the non-Windows path which correctly sets permissions on the temp file

Consider this fix:

 if onWindows {
-    err = acl.Chmod(filename, perm)
+    err = acl.Chmod(t.Name(), perm)
 } else {
     err = t.Chmod(perm)
 }

Let's verify the usage of acl.Chmod in similar scenarios across the codebase:

✅ Verification successful

Let me analyze the usage patterns of acl.Chmod to make a definitive decision. I need to check the context of the renameio package to understand the correct pattern.


Let me check the complete implementation of the temp file handling to understand the full context.


Review comment is correct - Windows permission setting needs to be fixed

The review comment correctly identified a critical issue. Looking at the reference implementation in base/utils/renameio/writefile.go, we can see that permissions should be set on the temporary file (t.path) before writing data, not on the target filename. This is consistent across the codebase where atomic file operations are performed.

The current implementation in fstree.go deviates from this pattern and could indeed cause issues when the target file doesn't exist yet. The suggested fix in the review comment to use t.Name() instead of filename is the correct approach, making it consistent with the reference implementation.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other instances of acl.Chmod usage to ensure consistent patterns
rg "acl\.Chmod" -A 3 -B 3

Length of output: 3330


Script:

#!/bin/bash
# Check the implementation of renameio WriteFile and related functions
rg -A 10 -B 10 "func WriteFile|func write" base/utils/renameio/

Length of output: 1196


Script:

#!/bin/bash
# Check the TempFile implementation and temp file path handling
cat base/utils/renameio/writefile.go

Length of output: 690

service/updates/upgrader.go (2)

14-14: LGTM: Appropriate package for Windows ACL handling

The addition of the go-acl package is aligned with the PR's objective to fix file permissions on Windows.


353-358: Verify consistent usage of acl.Chmod across the codebase

Let's ensure that all file permission operations on Windows use acl.Chmod consistently.

✅ Verification successful

Let me analyze the context further by checking the implementation details of these files.


Based on the analysis of the search results, I can now generate the final response:

Windows file permission handling is consistently implemented

The codebase shows a consistent pattern where acl.Chmod is used for Windows-specific file permission handling, while os.Chmod is used for non-Windows systems. The implementation in upgrader.go follows this pattern correctly:

  • All Windows-specific permission changes use acl.Chmod
  • The code is properly wrapped in if onWindows conditions
  • The same permission mode (0o0755) is consistently used across similar operations
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for direct os.Chmod calls that might need to be replaced with acl.Chmod for Windows
rg "os\.Chmod\(" --type go

# Search for existing acl.Chmod usage to verify consistency
rg "acl\.Chmod\(" --type go

Length of output: 1193


Script:

#!/bin/bash
# Check the implementation of os_integration files for Windows and Linux
rg -A 5 "package updates" service/updates/os_integration_

# Check if there are any Windows-specific files or conditions
rg -B 2 -A 2 "if.*[Ww]indows" --type go

Length of output: 11926

base/updater/fetch.go (2)

17-17: LGTM: Appropriate package for Windows ACL management

The addition of github.com/hectane/go-acl is a good choice for handling Windows file permissions.

🧰 Tools
🪛 golangci-lint (1.62.2)

17-17: File is not gci-ed with --skip-generated -s standard -s default

(gci)

🪛 GitHub Check: Linter

[failure] 17-17:
File is not gci-ed with --skip-generated -s standard -s default (gci)


140-145: LGTM: Proper Windows ACL handling implemented

The implementation correctly uses platform-specific APIs (acl.Chmod for Windows, os.Chmod for others) while maintaining consistent behavior across platforms. Error handling is appropriate with non-fatal warnings.

cmds/portmaster-start/run.go (2)

16-16: LGTM: Appropriate package for Windows ACL operations

The addition of github.com/hectane/go-acl is well-suited for handling Windows-specific file permissions.


276-281: ⚠️ Potential issue

Remove redundant early return and improve error handling

There are several issues in the permission handling logic:

  1. The early if onWindows { return nil } check at the beginning of the function conflicts with the new Windows-specific code path.
  2. Windows and non-Windows error cases should be handled separately for better debugging.

Apply this diff to fix the issues:

func fixExecPerm(path string) error {
-    if onWindows {
-        return nil
-    }

    info, err := os.Stat(path)
    if err != nil {
        return fmt.Errorf("failed to stat %s: %w", path, err)
    }

    if info.Mode() == 0o0755 {
        return nil
    }

    if runtime.GOOS == "windows" {
        err = acl.Chmod(path, 0o0755)
+       if err != nil {
+           return fmt.Errorf("failed to set Windows ACL permissions on %s: %w", path, err)
+       }
    } else {
        err = os.Chmod(path, 0o0755)
+       if err != nil {
+           return fmt.Errorf("failed to chmod %s: %w", path, err)
+       }
    }
-    if err != nil {
-        return fmt.Errorf("failed to chmod %s: %w", path, err)
-    }

    return nil
}

Let's verify the Windows ACL permission value compatibility:

✅ Verification successful

Let me analyze the search results and generate additional verification scripts to check the Windows ACL permission handling.

The first search will help identify the ACL permission patterns, while the second will show how permissions are handled across Windows and non-Windows platforms. This will help verify if the 0o0755 permission value is consistently and correctly handled.


Based on the search results, I can now provide a final response since I have enough information about how permissions are handled across the codebase:

The permission handling changes are correct and consistent with codebase patterns

The changes to the permission handling logic are valid and follow established patterns in the codebase:

  1. The 0o0755 permission value is consistently used across Windows and non-Windows platforms as shown in multiple files (base/updater/fetch.go, service/updates/upgrader.go, etc.)
  2. The Windows-specific acl.Chmod() vs os.Chmod() branching pattern is consistently implemented across the codebase
  3. The early return for Windows should indeed be removed as it conflicts with the proper Windows ACL permission handling that's now implemented
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if the codebase handles Windows ACL permissions consistently
# and if there are any Windows-specific permission patterns or constants defined.

# Search for Windows-specific permission handling
rg -A 3 'windows|acl\.Chmod|0o0755|0755' --type go

Length of output: 103178


Script:

#!/bin/bash
# Search for ACL permission handling patterns and constants
ast-grep --pattern 'acl.Chmod($path, $_)'

# Search for Windows-specific permission handling in the codebase
rg -A 3 'acl\.Chmod|os\.Chmod' --type go

Length of output: 3374

base/utils/fs.go Outdated
Comment on lines 41 to 46
}
return os.Chmod(path, perm)
if isWindows {
return acl.Chmod(path, perm)
} else if f.Mode().Perm() != perm {
return os.Chmod(path, perm)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix unreachable and potentially dangerous code

There are several issues in this section:

  1. The f variable is nil in this code path as we just created the directory
  2. The permission check is redundant as we just created the directory with the specified permissions
  3. The else condition would cause a nil pointer panic

Apply this fix:

 if isWindows {
   return acl.Chmod(path, perm)
-} else if f.Mode().Perm() != perm {
-  return os.Chmod(path, perm)
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
}
return os.Chmod(path, perm)
if isWindows {
return acl.Chmod(path, perm)
} else if f.Mode().Perm() != perm {
return os.Chmod(path, perm)
}
}
if isWindows {
return acl.Chmod(path, perm)
}

base/utils/fs.go Outdated
Comment on lines 24 to 28
if isWindows {
// TODO: set correct permission on windows
// acl.Chmod(path, perm)
return acl.Chmod(path, perm)
} else if f.Mode().Perm() != perm {
return os.Chmod(path, perm)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix potential nil pointer dereference in permission check

The permission check in the else condition could cause a panic when the file doesn't exist, as f would be nil.

Apply this fix:

 if isWindows {
   return acl.Chmod(path, perm)
-} else if f.Mode().Perm() != perm {
+} else if f != nil && f.Mode().Perm() != perm {
   return os.Chmod(path, perm)
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if isWindows {
// TODO: set correct permission on windows
// acl.Chmod(path, perm)
return acl.Chmod(path, perm)
} else if f.Mode().Perm() != perm {
return os.Chmod(path, perm)
}
if isWindows {
return acl.Chmod(path, perm)
} else if f != nil && f.Mode().Perm() != perm {
return os.Chmod(path, perm)
}

Comment on lines 50 to 54
if runtime.GOOS == "windows" {
err = acl.Chmod(sandboxFile, 0o0755|os.ModeSetuid)
} else {
err = os.Chmod(sandboxFile, 0o0755|os.ModeSetuid)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Critical: Inconsistent OS handling and unnecessary Windows logic

There are several issues with the Windows-specific changes:

  1. The function documentation states it's Linux-specific, but now includes Windows logic
  2. There's an early return for non-Linux systems (line 26) that makes this Windows-specific code unreachable
  3. Setting SUID bit on Windows is unnecessary as Windows doesn't use the SUID concept
  4. The Unix permission mask might not translate correctly to Windows ACLs

Consider removing the Windows-specific code since:

  1. The function is designed for Linux-specific Chrome sandbox SUID requirements
  2. The early return makes this code unreachable
  3. SUID permissions aren't applicable to Windows
-	if runtime.GOOS == "windows" {
-		err = acl.Chmod(sandboxFile, 0o0755|os.ModeSetuid)
-	} else {
-		err = os.Chmod(sandboxFile, 0o0755|os.ModeSetuid)
-	}
+	err = os.Chmod(sandboxFile, 0o0755|os.ModeSetuid)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if runtime.GOOS == "windows" {
err = acl.Chmod(sandboxFile, 0o0755|os.ModeSetuid)
} else {
err = os.Chmod(sandboxFile, 0o0755|os.ModeSetuid)
}
err = os.Chmod(sandboxFile, 0o0755|os.ModeSetuid)

Comment on lines 46 to 51
if runtime.GOOS == "windows" {
err = acl.Chmod(path.Join(opts.TempDir, dest), opts.Mode)
} else {
err = tmpFile.Chmod(opts.Mode)
}
if err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use filepath.Join instead of path.Join for cross-platform compatibility.

The path package is designed for slash-separated paths (like URLs), while filepath is designed for OS-specific path manipulation. On Windows, using path.Join could lead to incorrect path separators.

-import "path"
+import "path/filepath"

-			err = acl.Chmod(path.Join(opts.TempDir, dest), opts.Mode)
+			err = acl.Chmod(filepath.Join(opts.TempDir, dest), opts.Mode)

Committable suggestion skipped: line range outside the PR's diff.


💡 Codebase verification

🛠️ Refactor suggestion

The review comment is correct. The temporary file path is incorrectly constructed.

Based on the code analysis:

  1. renameio.TempFile creates a temporary file with pattern .{basename} in the directory determined by tempDir function
  2. The tempDir function either uses the caller-specified directory or falls back to the destination file's directory
  3. The current code incorrectly assumes the temp file is at TempDir/dest, but it's actually at a path returned by tmpFile.Name()

The suggested fix to use tmpFile.Name() is the correct approach as it will use the actual temporary file path rather than an incorrect assumption about its location.

🔗 Analysis chain

Verify the temporary file path construction.

The code attempts to chmod the file at TempDir/dest, but renameio.TempFile might use a different path pattern for the temporary file. This could result in permission changes being applied to the wrong file.

Additionally, consider refactoring to use the actual temporary file path:

 	if opts.Mode != 0 {
 		if runtime.GOOS == "windows" {
-			err = acl.Chmod(path.Join(opts.TempDir, dest), opts.Mode)
+			err = acl.Chmod(tmpFile.Name(), opts.Mode)
 		} else {
 			err = tmpFile.Chmod(opts.Mode)
 		}

Let me check how the tempDir function constructs the temporary directory path.


Let me check the complete implementation of tempDir to understand how it determines the final temporary directory path.


Let me check how the TempFile is being used in the atomic.go file to understand the complete context.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check how renameio.TempFile constructs temporary paths
rg -A 10 'func TempFile' --type go

Length of output: 762


Script:

#!/bin/bash
# Check how tempDir function constructs temporary directory paths
rg -A 5 'func tempDir' --type go

Length of output: 411


Script:

#!/bin/bash
# Get the complete tempDir function implementation
rg -A 15 'func tempDir' --type go

Length of output: 1109


Script:

#!/bin/bash
# Get the context of TempFile usage in atomic.go
rg -B 10 -A 10 'TempFile' base/utils/atomic.go

Length of output: 807

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (1)
service/updates/helper/electron.go (1)

54-54: Consider OS-specific debug logging

The debug message about fixing SUID permissions is misleading on non-Linux systems where this function returns early.

-	log.Debugf("updates: fixed SUID permission for chrome-sandbox")
+	log.Debugf("updates: fixed SUID permission for chrome-sandbox on Linux")
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 1cf591a and 5497595.

📒 Files selected for processing (3)
  • base/utils/atomic.go (2 hunks)
  • cmds/portmaster-start/run.go (3 hunks)
  • service/updates/helper/electron.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • base/utils/atomic.go
  • cmds/portmaster-start/run.go
🧰 Additional context used
🪛 GitHub Check: Linter
service/updates/helper/electron.go

[failure] 11-11:
"github.com/hectane/go-acl" imported as acl and not used (typecheck)


[failure] 11-11:
"github.com/hectane/go-acl" imported as acl and not used) (typecheck)

🔇 Additional comments (1)
service/updates/helper/electron.go (1)

Line range hint 22-57: Implementation doesn't align with PR objectives

This function is explicitly designed for Linux systems (as stated in the documentation) and includes an early return for non-Linux systems. However, the PR's objective is to "Fix file permissions on windows". There seems to be a disconnect between the PR's purpose and this implementation.

Consider:

  1. Either this function should remain Linux-specific (current state)
  2. Or it needs to be refactored to handle Windows-specific requirements, which would involve:
    • Updating the function documentation
    • Removing the early return for non-Linux systems
    • Implementing proper Windows permission handling using acl.Chmod

Let's verify if this function is called on Windows systems:

@@ -8,6 +8,7 @@
"runtime"
"strings"

"github.com/hectane/go-acl"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove unused import

The go-acl package is imported but never used in this file. This appears to be an oversight, possibly from an incomplete implementation of Windows-specific permission handling.

-	"github.com/hectane/go-acl"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"github.com/hectane/go-acl"
🧰 Tools
🪛 GitHub Check: Linter

[failure] 11-11:
"github.com/hectane/go-acl" imported as acl and not used (typecheck)


[failure] 11-11:
"github.com/hectane/go-acl" imported as acl and not used) (typecheck)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 5497595 and 447df43.

📒 Files selected for processing (3)
  • base/utils/atomic.go (2 hunks)
  • cmds/portmaster-start/run.go (3 hunks)
  • service/updates/helper/electron.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • cmds/portmaster-start/run.go
  • service/updates/helper/electron.go
🧰 Additional context used
🪛 golangci-lint (1.62.2)
base/utils/atomic.go

12-12: File is not gci-ed with --skip-generated -s standard -s default

(gci)

🪛 GitHub Check: Linter
base/utils/atomic.go

[failure] 12-12:
File is not gci-ed with --skip-generated -s standard -s default (gci)

🔇 Additional comments (1)
base/utils/atomic.go (1)

9-12: LGTM: Import changes are appropriate

The added imports support the Windows-specific permission handling requirements correctly.

🧰 Tools
🪛 golangci-lint (1.62.2)

12-12: File is not gci-ed with --skip-generated -s standard -s default

(gci)

🪛 GitHub Check: Linter

[failure] 12-12:
File is not gci-ed with --skip-generated -s standard -s default (gci)

Comment on lines 46 to 51
if runtime.GOOS == "windows" {
err = acl.Chmod(filepath.Join(opts.TempDir, dest), opts.Mode)
} else {
err = tmpFile.Chmod(opts.Mode)
}
if err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Critical: Incorrect temporary file path used for Windows permissions

The code attempts to set permissions on filepath.Join(opts.TempDir, dest), but this is not the actual path of the temporary file. The temporary file's actual path is available via tmpFile.Name().

Apply this fix to ensure permissions are set on the correct file:

 	if opts.Mode != 0 {
 		if runtime.GOOS == "windows" {
-			err = acl.Chmod(filepath.Join(opts.TempDir, dest), opts.Mode)
+			err = acl.Chmod(tmpFile.Name(), opts.Mode)
 		} else {
 			err = tmpFile.Chmod(opts.Mode)
 		}

This ensures that:

  1. Permissions are set on the actual temporary file
  2. The behavior is consistent with the non-Windows path where tmpFile.Chmod is used
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if runtime.GOOS == "windows" {
err = acl.Chmod(filepath.Join(opts.TempDir, dest), opts.Mode)
} else {
err = tmpFile.Chmod(opts.Mode)
}
if err != nil {
if runtime.GOOS == "windows" {
err = acl.Chmod(tmpFile.Name(), opts.Mode)
} else {
err = tmpFile.Chmod(opts.Mode)
}
if err != nil {

💡 Codebase verification

Based on the gathered information, I can now provide a final response about the temporary file handling implementation:

Incorrect path handling in Windows-specific code

The current implementation has a path handling issue in the Windows-specific branch. When calling acl.Chmod(filepath.Join(opts.TempDir, dest), opts.Mode), it incorrectly reconstructs the temporary file path. The actual temporary file is created by renameio.TempFile() which:

  1. Uses tempDir() function to determine the correct temporary directory
  2. Creates the temp file with a unique name using os.CreateTemp() with pattern "."+filepath.Base(path)

The Windows branch should use the actual temporary file path from the tmpFile object instead of trying to reconstruct it. This could lead to permission setting failures on Windows as it's targeting the wrong path.

Affected locations:

  • base/utils/atomic.go:48: Should use tmpFile.Name() instead of filepath.Join(opts.TempDir, dest)
🔗 Analysis chain

Verify temporary file handling implementation

Let's verify how the temporary files are created and managed to understand the full impact of the path issue.


Let me gather more information about the temporary directory handling to fully understand the path construction.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check renameio.TempFile implementation and usage patterns

# Check TempFile implementation
echo "Checking TempFile implementation:"
rg -A 10 'func TempFile' --type go

# Check for other instances of similar permission handling
echo "Checking for similar permission patterns:"
ast-grep --pattern 'if runtime.GOOS == "windows" {
  $$$
  acl.Chmod($_, $_)
  $$$
}'

Length of output: 1020


Script:

#!/bin/bash
# Check tempDir implementation and related path handling
echo "Checking tempDir implementation:"
rg -A 10 'func tempDir' --type go

# Check atomic write implementation context
echo "Checking atomic write implementation:"
rg -B 5 -A 10 'TempFile\(' --type go

# Check for any other Windows-specific path handling
echo "Checking Windows-specific path handling:"
rg 'filepath.Join.*windows' --type go

Length of output: 10327

dhaavi
dhaavi previously approved these changes Nov 26, 2024
Copy link
Member

@dhaavi dhaavi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One question.

@@ -40,7 +43,12 @@ func CreateAtomic(dest string, r io.Reader, opts *AtomicFileOptions) error {
defer tmpFile.Cleanup() //nolint:errcheck

if opts.Mode != 0 {
if err := tmpFile.Chmod(opts.Mode); err != nil {
if runtime.GOOS == "windows" {
err = acl.Chmod(filepath.Join(opts.TempDir, dest), opts.Mode)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you really mean to change the permission of the directory and not the permissions of the file?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (1)
base/utils/fs.go (1)

25-27: Improve error handling and documentation

While ignoring permission errors for non-admin users is intentional, consider these improvements:

  1. Log the error at debug level for troubleshooting
  2. Fix the typo in the comment ("none" should be "non")
-				// Ignore windows permission error. For none admin users it will always fail.
-				acl.Chmod(path, perm)
+				// Ignore windows permission error. For non-admin users it will always fail.
+				if err := acl.Chmod(path, perm); err != nil {
+					// Log error at debug level since it's expected for non-admin users
+					log.Debugf("Failed to set permissions on %s: %v", path, err)
+				}
🧰 Tools
🪛 GitHub Check: Linter

[failure] 26-26:
Error return value is not checked (errcheck)

🪛 golangci-lint (1.62.2)

26-26: Error return value is not checked

(errcheck)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 67b74e6 and 7d0171b.

📒 Files selected for processing (2)
  • base/utils/fs.go (3 hunks)
  • cmds/portmaster-start/run.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmds/portmaster-start/run.go
🧰 Additional context used
🪛 GitHub Check: Linter
base/utils/fs.go

[failure] 26-26:
Error return value is not checked (errcheck)


[failure] 46-46:
Error return value is not checked (errcheck)

🪛 golangci-lint (1.62.2)
base/utils/fs.go

26-26: Error return value is not checked

(errcheck)


46-46: Error return value is not checked

(errcheck)

🔇 Additional comments (2)
base/utils/fs.go (2)

9-10: LGTM: Clean import addition

The go-acl package is properly imported for Windows-specific permission handling.


Line range hint 16-54: Verify cross-platform permission behavior

The changes look good overall, but we should verify that the permission handling works consistently across platforms.

✅ Verification successful

Permission handling is consistent across the codebase

The verification shows that permission handling follows a consistent pattern across the codebase:

  • Windows: Uses acl.Chmod() with ignored errors for non-admin users
  • Non-Windows: Uses os.Chmod() with proper error handling

The implementation in EnsureDirectory() aligns with this established pattern, as evidenced by similar implementations in:

  • base/database/storage/fstree/fstree.go
  • base/utils/renameio/writefile.go
  • base/updater/fetch.go
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other uses of os.Chmod to ensure consistent handling across the codebase
rg --type go 'os\.Chmod|acl\.Chmod' -B 2 -A 2

# Look for any related permission-setting patterns
ast-grep --pattern 'if isWindows {
  $$$
}'

Length of output: 4630

🧰 Tools
🪛 GitHub Check: Linter

[failure] 46-46:
Error return value is not checked (errcheck)

🪛 golangci-lint (1.62.2)

46-46: Error return value is not checked

(errcheck)

Comment on lines +44 to +50
if isWindows {
// Ignore windows permission error. For none admin users it will always fail.
acl.Chmod(path, perm)
return nil
} else {
return os.Chmod(path, perm)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor to reduce duplication and simplify control flow

The Windows permission handling code is duplicated and the control flow could be simplified.

-		if isWindows {
-			// Ignore windows permission error. For none admin users it will always fail.
-			acl.Chmod(path, perm)
-			return nil
-		} else {
-			return os.Chmod(path, perm)
-		}
+		if isWindows {
+			// Ignore windows permission error. For non-admin users it will always fail.
+			if err := acl.Chmod(path, perm); err != nil {
+				log.Debugf("Failed to set permissions on %s: %v", path, err)
+			}
+			return nil
+		}
+		return os.Chmod(path, perm)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if isWindows {
// Ignore windows permission error. For none admin users it will always fail.
acl.Chmod(path, perm)
return nil
} else {
return os.Chmod(path, perm)
}
if isWindows {
// Ignore windows permission error. For non-admin users it will always fail.
if err := acl.Chmod(path, perm); err != nil {
log.Debugf("Failed to set permissions on %s: %v", path, err)
}
return nil
}
return os.Chmod(path, perm)
🧰 Tools
🪛 GitHub Check: Linter

[failure] 46-46:
Error return value is not checked (errcheck)

🪛 golangci-lint (1.62.2)

46-46: Error return value is not checked

(errcheck)

@vlabo vlabo merged commit 943b9b7 into develop Nov 26, 2024
5 of 6 checks passed
vlabo added a commit that referenced this pull request Nov 28, 2024
* [service] Set file permissions on windows

* [service] Fix minor windows permission bugs

* [service] Fix permission bugs

* [service] Fix windows non admin user start
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants