Assuming we have some PID and absolute file path [not a symlink, just a regular file] - what is the most efficient way to determine that PID has read access to this file?
I'm only aware of one way to do this. First, find the UID and GID of the process by constructing the path /proc/ + the PID. For example /proc/4261. You then stat() that path and get its UID and GID. Then, you stat() the file you want to check for read access and check whether the UID/GID of the process has read permissions:
(It is assumed you already constructed the "/proc/[PID]" path in path_to_proc.)
struct stat buf;
// Get UID and GID of the process.
stat(path_to_proc, &buf);
uid_t proc_uid = buf.st_uid;
gid_t proc_gid = buf.st_gid;
// Get UID and GID of the file.
stat(path_to_file_you_want_to_check, &buf);
// If the process owns the file, check if it has read access.
if (proc_uid == buf.st_uid && buf.st_mode & S_IRUSR) {
// Yes, the process has read access.
}
// Check if the group of the process's UID matches the file's group
// and if so, check for read/write access.
else if (proc_gid == buf.st_gid && buf.st_mode & S_IRGRP) {
// Yes, the process has read access.
}
// The process's UID is neither the owner of the file nor does its GID
// match the file's. Check whether the file is world readable.
else if (buf.st_mode & S_IROTH) {
// Yes, the process has read access.
}
Note that the code is not perfect. It does not handle the possibility that the user of the process actually belongs to the file's group without it being the user's primary group. To deal with that, you will need to make use of getgrouplist() (which means you will need to convert the process UID to a string containing the actual username first, and then compare all returned groups to the file's group and if one matches, check for group read access (S_IRGRP).)
Open the file. That's really the only way to know. The answers involving stat(2) require that you write code to interpret the permissions bits and compare them to your active uid/gid and supplemental groups. And in any case it is incomplete in the general case: LSM hooks like selinux or apparmor can also implement permissions models on files that are not captured by the traditional Unix permissions model.
Related
I'm having a bunch of processes from different privileges, all running a shared code that open (and create if needed) a file for write using fopen_s with "a+" flag.
However, since no permissions that supplied to this command, and a root process create the file first, than other non-root processes couldn't access this file.
I could use int open(const char *pathname, int flags, mode_t mode); and thus control the file permissions (represented by mode_t) to allow access for everyone, but I need the file descriptor (FILE *) and not fileID. so I can use FILE *fdopen(int fd, const char *mode); in order to make the conversion.
Perhaps there's a more straight forward way to do it ?
No. The technique you described (open followed by fdopen) is the correct way to achieve what you want to do. As Some programmer dude pointed out, you could call chmod from your program to change the file permissions after it's created, but that's a more roundabout way to do it.
I could use int open(const char *pathname, int flags, mode_t mode); and thus control the file permissions (represented by mode_t)
Not really. Unless you set your process's umask setting. Because the permissions passed to open() are not the permissions the created file is necessarily created with.
Per POSIX open() (bolding mine):
the access permission bits (see <sys/stat.h>) of the file mode shall be set to the value of the argument following the oflag argument taken as type mode_t modified as follows: a bitwise AND is performed on the file-mode bits and the corresponding bits in the complement of the process' file mode creation mask. Thus, all bits in the file mode whose corresponding bit in the file mode creation mask is set are cleared.
So
int fd = open( someFileName, O_CREAT | O_RDWR, 0644 );
is NOT guaranteed to set the file permissions to 0644.
If your file creation mask is set to 0077, then the file will actually be created with permissions set to 0600.
Note that the umask() setting is a process-wide property, and it's not really a good idea to change it much. And if you're trying to write general-purpose code that has no side effects, it's a bad idea to change it at all. For example, changing the umask() setting in a multithreaded process in order to allow wider access to files being created can cause
security problems if another thread creates a file at the same time.
The best way to set file permissions to be exactly what you want is to set file permissions to be exactly what you want with fchmod():
FILE *f = fopen(...);
fchmod( fileno( f ), 0644 );
In fact, since the umask() setting is a process-wide property, it's always possible in general that it can be changed by another thread at any time, so setting the permissions explicitly via chmod() or fchmod() is the only guaranteed way to get exactly the permissions specified in all circumstances.
How to avoid TOCTOU(time-of-check, time-of-use) race condition for race condition between stat and rename for LOGFILE ?
Required to move the log file after its size value exceeds the max size.
result = stat(LOGFILE, & data);
if (result != 0) {
// stat failed
// file probably does not exist
} else if (data.st_size > MAX_LOGSIZE) {
unlink(PREV_LOGFILE);
(void) rename(LOGFILE, PREV_LOGFILE);
}
The standard way to avoid TOCTTOU on file operations is to open the file once and then do everything that you need through the file descriptor rather than the file name.
However, both renaming and unlinking a file require its path (because they need to know what link to rename or remove), so you can't use that approach here. An alternative might be to copy the file's contents elsewhere and then truncate it to zero bytes, although your scenario with log files probably requires that the operation be atomic, which may be difficult to achieve. Another approach is to require tight access controls on the directory: if an attacker cannot write to the directory, then it cannot play TOCTTOU games with your process. You can use unlinkat and renameat to restrict your paths to a specific directory's file descriptor so that you don't need to worry about the directory itself changing.
Something like this untested code might do the job, assuming a POSIX-like platform:
dirfd = open(LOGDIR, O_DIRECTORY);
// check for failure
res = fstatat(dirfd, LOGFILE, statbuf, AT_SYMLINK_NOFOLLOW);
if ((0 == res) && (S_ISREG(statbuf) && (data.st_size > MAX_LOGSIZE)) {
unlinkat(dirfd, PREV_LOGFILE, 0);
renameat(dirfd, LOGFILE, dirfd, PREV_LOGFILE);
}
close(dirfd);
First of all, if there is an easier way than using inotify, please tell me!
Basically what I would like to do is watching a root directory with inotify with these flags: IN_CREATE | IN_MODIFY | IN_DELETE.
When it's IN_CREATE and IN_ISDIR I would like to watch that folder too. But the main thing I need is whether a file was created, deleted or modified even in subdirectories. Now I know I can just add multiple directories with inotify_add_watch(), but when I read the event->name how can I know which directory it belongs to? The inotify_event struct doesn't seem to hold that value. So if I have a structure like this:
/root
Then I create a directory "a":
/root/a
Then create a file:
/root/a/tmp.txt
When I read event->name it'll only say tmp.txt, but how can I know it is in the "a" subdirectory? How can I know what the watch descriptor was?
In inotify_event structure the name field contains the name of the object to which the event occurred, relative to wd. You need to get the absolute path of the parent directory and concatenate the name of the file/directory to get the full path.
Also in Inotify_event structure the mask field, you can use the IN_ISDIR mask bit to know if the event that has occurred for that wd is a file or a directory.
This is from the inotify
here
The name field is only present when an event is returned for a file inside a watched directory; it identifies the file pathname relative to the watched directory. This pathname is null-terminated, and may include further null bytes to align subsequent reads to a suitable address boundary.
Here's how I did it:
I created a hashmap (QHash<int, QString> fd_to_path) during inotify_add_watch() time to couple the received wd with its corresponding directory string:
int wd = inotify_add_watch(...next_dir_path..);
if (wd != -1)
fd_to_path.insert(wd, next_dir_path);
Then when reading the received inotify event after struct inotify_event *ev = (...); you just query the corresponding dir path with:
QString dir_path = fd_to_path.value(ev->wd);
I've written a very simple service application based on this code example.
The application as part of its normal running assumes there exists a file in the directory it is found, or in its execution path.
When I 'install' the service and then subsequently 'start' the service from the service manager in control panel. The application fails because it can't find the file to open and read from (even though the file is in the same directory as the installed executable).
My question is when a windows service is run, which is the expected running path supposed to be?
When calling 'CreateService' there only seems to be a path parameter for the binary, not for execution. Is there someway to indicate where the binary should be executed from?
I've tried this on windows vista and windows 7. Getting the same issues.
Since Windows services are run from a different context than normal user-mode applications, it's best if you don't make any assumptions about working directories or relative paths. Aside from differences in working directories, a service could run using a completely different set of permissions, etc.
Using an absolute path to the file that your service needs should avoid this problem entirely. Absolute paths will be interpreted the same regardless of the working directory, so this should make the working directory of your service irrelevant. There are several ways to go about this:
Hard-code the absolute path - This is perhaps the easiest way to avoid the problem, however it's also the least flexible. This method is probably fine for basic development and testing work, but you probably want something a bit more sophisticated before other people start using your program.
Store the absolute path in an environment variable - This gives you an extra layer of flexibility since the path can now be set to any arbitrary value and changed as needed. Since a service can run as a different user with a different set of environment variables, there are still some gotchas with this approach.
Store an absolute path in the registry - This is probably the most fool-proof method. Retrieving the path from the registry will give you the same result for all user accounts, plus this is relatively easy to set up at install time.
By default, the current directory for your Windows service is the System32 folder.
A promising solution is creating an environment variable that keeps the full path of your input location and retrieving the path from this variable at runtime.
If you use the same path as binary, you could just read binary path and modify it accordingly. But this is rather quick-fix rather than designed-solution. If I were you, I would either create system-wide environment variable and store value there, or (even better) use windows registry to store service configuration.
Note:
You will need to add Yourself some privileges using AdjustTokenPrivileges function, you can see an example here in ModifyPrivilege function.
Also be sure to use HKEY_LOCAL_MACHINE and not HKEY_CURRENT_USER. Services ar running under different user account so it's HKCU's will be different than what you can see in your registry editor.
Today I solved this problem as it was needed for some software I was developing.
As people above have said; you can hardcode the directory to a specific file - but that would mean whatever config files are needed to load would have to be placed there.
For me, this service was being installed on > 50,000 computers.
We designed it to load from directory in which the service executable is running from.
Now, this is easy enough to set up and achieve as a non-system process (I did most of my testing as a non-system process). But the thing is that the system wrapper that you used (and I used as well) uses Unicode formatting (and depends on it) so traditional ways of doing it doesn't work as well.
Commented parts of the code should explain this. There are some redundancies, I know, but I just wanted a working version when I wrote this.
Fortunately, you can just use GetModuleFileNameA to process it in ASCII format
The code I used is:
char buffer[MAX_PATH]; // create buffer
DWORD size = GetModuleFileNameA(NULL, buffer, MAX_PATH); // Get file path in ASCII
std::string configLoc; // make string
for (int i = 0; i < strlen(buffer); i++) // iterate through characters of buffer
{
if (buffer[i] == '\\') // if buffer has a '\' in it, replace with doubles
{
configLoc = configLoc + "\\\\"; // doubles needed for parsing. 4 = 2(str)
}
else
{
configLoc = configLoc + buffer[i]; // else just add char as normal
}
}
// Complete location
configLoc = configLoc.substr(0, configLoc.length() - 17); //cut the .exe off the end
//(change this to fit needs)
configLoc += "\\\\login.cfg"; // add config file to end of string
From here on, you can simple parse configLoc into a new ifsteam - and then process the contents.
Use this function to adjust the working directory of the service to be the same as the working directory of the exe it's running.
void AdjustCurrentWorkingDir() {
TCHAR szBuff[1024];
DWORD dwRet = 0;
dwRet = GetModuleFileName(NULL, szBuff, 1024); //gets path of exe
if (dwRet != 0 && GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
*(_tcsrchr(szBuff, '\\') + 1) = 0; //get parent directory of exe
if (SetCurrentDirectory(szBuff) == 0) {
//Error
}
}
}
I'm modifying an old C++ program to run on Vista. It does not require Admin privileges.
I've changed the code to put logfiles in \ProgramData\MyApp\. These logfiles are written with the stdio functions (fopen, fprintf, fclose).
Here's the problem:
UserA runs the program first, it creates \ProgramData\MyApp\MyLogFile.txt using CreateFile()
UserB runs the program next, it tries to append to MyLogFile.txt and gets access denied.
I tried creating a null SECURITY_DESCRIPTOR and passing that to CreateFile(). That does create a file with "no permissions assigned", but it seems as if the first user to write to the file takes ownership and afterwards all the other non-admin users are out of luck.
It's important that all users share the same logfiles, but it's also important that I change as little code as possible.
Edited to add:
\ProgramData\MyApp is created by a standard Visual Studio installer. (I don't see any place to set directory security.) When it creates \MyApp it grants Users these permissions:
Read & execute
List folder contents
Read
Special permissions
Under Advanced I see that Special permissions includes:
Create files / write data
Create folders / append data
Write attributes
Write extended attributes
+1 to everyone for trying, but eventually I found the answer here:
how to change the ACLs from c++?
I did have to change one line of that solution, from this:
ea[0].grfAccessMode = DENY_ACCESS;
to this:
ea[0].grfAccessMode = GRANT_ACCESS;
Probably that your application uses an installer. When the installer creates your folder "MyApp", assign read/write rights for everyone. This will probably fix your problem. There are different ways of doing this, but it depends on the type of the setup that you use.
Added custom action info.
If after install the folder does not have the required permissions you could add for example a custom action as a visual basic script on the install sequence, that will set the required permissions.
VBS Examble:
Function SetPermissions()
Dim strHomeFolder, strHome, strUser
Dim intRunError, objShell, objFSO
strHomeFolder = "C:\Test"
Set objShell = CreateObject("Wscript.Shell")
Set objFSO = CreateObject("Scripting.FileSystemObject")
If objFSO.FolderExists(strHomeFolder) Then
intRunError = objShell.Run("%COMSPEC% /c Echo Y| cacls " _
& strHomeFolder & " /t /c /g everyone:F ", 2, True)
If intRunError <> 0 Then
Wscript.Echo "Error assigning permissions for user " _
& strUser & " to home folder " & strHomeFolder
End If
End If
End Function
You need to add an allow rule for the user "Everyone" if that is what you truly want.
A null descriptor will defer to the directory's security if memory serves...
You must definetely use CreateFile. See more about security and access rights. I am sure that functions from the standard C library use CreateFile (it can't use anything else on Windows) but with default security parameters which are not helpful in your case.
I tried also to look inside SECURITY_ATTRIBUTES and SECURITY_DESCRIPTOR structures but it's not very easy to understand how to do it, though it may be a chance.