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

Setuid as user for OsFs operations #1225

Closed
wants to merge 3 commits into from

Conversation

peterverraedt
Copy link

This PR proposes to change the behaviour of setting a uid/gid for a user, if sftpgo is run as root. If a user would create or modify a file, we act as the uid/gid to execute the modification, instead of acting as root and chowning the resulting file later. It is implemented in the virtual file system layer by calling syscall.Seteuid and syscall.Setegid before the operations.

The main benefit is that file permissions on the underlying file system will be honoured, as well as the virtual permissions that are checked by sftpgo itself. Only for creating the top user directory, we'll still act as root to ensure its existence.

The only exception is the ScanQuota function for virtual folders that is ran as root if triggered by an event, but that operation only reads files so it should be fine.

In case you would consider to merge this, I'm willing to invest time in implementing secondary groups as well.

@drakkan
Copy link
Owner

drakkan commented Mar 10, 2023

Hello, thanks for the patch. I initially considered this approach, but was scared about performance penalty. I never did a real test but the linked issue was enough for me to not use this method. I'm not sure

Signed-off-by: Peter Verraedt <[email protected]>
@peterverraedt
Copy link
Author

I'll try to test performance differences for some example code. I was hoping that by putting the LockOSThread and UnlockOSThread only in the functions that call out to the file system, and that themselves do not spawn (too much) goroutines, the go executer would not be confused too much.

The only real alternative would be to spawn a whole subprocess to handle an incoming connection of a user, but that is nontrivial as it requires some custom messaging passing between the main process an its children.

@drakkan
Copy link
Owner

drakkan commented Mar 10, 2023

The only real alternative would be to spawn a whole subprocess to handle an incoming connection of a user, but that is nontrivial as it requires some custom messaging passing between the main process an its children.

this will give even worse performance. If I understand your use case correctly, you need a way to set a different umask for each user. Did I get it right?

@codecov
Copy link

codecov bot commented Mar 10, 2023

Codecov Report

Merging #1225 (1779565) into main (dad346c) will decrease coverage by 0.01%.
The diff coverage is 100.00%.

@@            Coverage Diff             @@
##             main    #1225      +/-   ##
==========================================
- Coverage   98.88%   98.88%   -0.01%     
==========================================
  Files          75       75              
  Lines       25005    24981      -24     
==========================================
- Hits        24727    24703      -24     
  Misses        225      225              
  Partials       53       53              
Impacted Files Coverage Δ
internal/common/connection.go 97.55% <ø> (-0.01%) ⬇️
internal/ftpd/handler.go 100.00% <ø> (ø)
internal/httpd/handler.go 100.00% <ø> (ø)
internal/sftpd/handler.go 99.04% <ø> (-0.02%) ⬇️
internal/sftpd/scp.go 97.34% <ø> (-0.02%) ⬇️
internal/webdavd/handler.go 100.00% <ø> (ø)
internal/common/eventmanager.go 93.67% <100.00%> (-0.01%) ⬇️
internal/httpd/api_quota.go 100.00% <100.00%> (ø)

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

@peterverraedt
Copy link
Author

My usecase is a shared filesystem that is also accessible via other methods, and file permissions are therefore kept on the filesystem itself. There are shared folders with setuid bit set, so new files will inherit the group of the folder. There also can be file acls. Al these kind of permissions should be checked, as if gosftp would act as the user.

I was testing performance impact, and it seems to be 7-10 times slower (obviously because of the syscalls). But more problematic, go does not behave deterministic when doing setegid in multiple parallel goroutines, what LockOSThread should prevent. It doesn't happen if I only do seteuid but of course that is not really helpfull.

@peterverraedt
Copy link
Author

If I put a global lock for the part where seteuid and setegid are called, it is working fine. Such a global lock is not implemented in the PR yet.

Performance impact varies from 7x for non concurrent to 58x for heavily concurrent operations.
See https://gist.github.com/peterverraedt/1507f8d46d5a45760ee8bea36db9b159 for my test script. If you remove the global lock, you'll see panics.

@peterverraedt
Copy link
Author

I'll invest some time to create a library that mimicks the os.OpenFile/... behaviour as if a user would call it. Redoing the file system permission checks in user space will be more efficient.

@drakkan
Copy link
Owner

drakkan commented Mar 10, 2023

I'll invest some time to create a library that mimicks the os.OpenFile/... behaviour as if a user would call it. Redoing the file system permission checks in user space will be more efficient.

This seems a good idea. Your use case is quite complex, we can't fix it by just adding a umask to SFTPGo users. Please notify me when you will have something usable. Thanks

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