Assume I have a process with PID 1234 running in the background under user A.
If I run the following program as user A, it succeeds. If I run it as user B, it fails with open: Permission denied.
This makes sense, as the environ file is owned by user A and has read permission only for A.
But if I make the program set-user-ID for user A and run it as user B, it fails with read: Permission denied. This doesn't seem to happen with a regular file having the same permissions. It also doesn't happen if A is root.
Any ideas why? Is there any other way to get the environment of another process that works around this issue?
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, const char *argv[])
{
unsigned char ch = 0;
int fd = -1;
int read_result = -1;
setresuid(geteuid(), geteuid(), geteuid());
fd = open("/proc/1234/environ", O_RDONLY);
if (-1 == fd) {
perror("open");
return EXIT_FAILURE;
}
read_result = read(fd, &ch, 1);
if (-1 == read_result) {
perror("read");
return EXIT_FAILURE;
}
close(fd);
return EXIT_SUCCESS;
}
As you can see, if your program run without SETUID, open(2) gives you Permission denied, whereas if you run the program with SETUID, open(2) works ok, but read(2) causes the same error. This happens because of additional permission check during each file operation on /proc/* inodes. Looks like this additional permission check uses something other than EUID of the running process. If you run GNU/Linux, for more details see NOTE at the beginning of the code in <kernel_source>/fs/proc/base.c and environ_read() function in the same file.
One of the possible quick solutions:
set owner of the program file to root
set owner group to some special group
add user that should run the program (user B) to that special group
set mode bits to 4550 (r-sr-x---)
call setuid(getuid()) to drop priveleges as soon as possible, i.e. right after reading environ file
In this case any user from the given group could read /proc/*/environ of any other user.
If you want to reduce the permissions of your program to allow only read environ files of the specific user (user A), you probably should think of some other tricks. For example config file, containing the user(s) whose environ file(s) could be read.
Always be careful with extra permissions. Especially with root permissions. Do necessary privileged operations and drop permissions as soon as possible.
Related
I'm trying to write a C program which is supposed to open a file which can only be read/written to by (non-root) User A. When run by users who are neither root nor User A, the program should allow the user to open the file with the effective user being User A.
Currently, my program is able to change euid only if it is run as sudo, which makes sense.
int main(int argc, char const *argv[]) {
// should be userB's uid
uid_t ruid = getuid();
uid_t fake_uid = <userA-uid>;
seteuid(fake_uid);
/* open, read, write to and close file here */
// returning euid to original uid
seteuid(ruid);
return 0;
}
Consider using setuid to userA:
chown userA program
chmod 4555 program
Then the program can drop the privilege as soon as it opens the file:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
static void drop_privileges(void)
{
uid_t uid = getuid();
if (seteuid(uid)) {
perror("drop_privileges");
abort();
}
}
int main()
{
/* ... privileged operations */
drop_privileges();
/* ... rest */
}
I am running a process (Process A) which opens a file with read only access mode. Then it pauses, causing the process to stay running and keep the file descriptor open. Later, if any of the below is possible, it will resume after some time and continue operation.
I want to know if any of these are possible:
Can we create another process (Process B) with superuser privileges, which can access Process A's open file descriptor and change its access mode to read and write ?
Can we modify the file descriptor of Process A, from within the process (I mean within its code) from read only to read and write?
Can I create a loadable kernel module that access the process A using its process ID (PID) and check for open file descriptors and change their permissions to read and write?
I have searched the forums countless number of times, but didn't find anything specific to my problem. I also found out about the fcntl() system call. But this doesn't allow us to modify the status flag of a file descriptor.
You can't. Permissions are locked in place at the time when a file descriptor is opened and it would be almost impossible to ensure the security of the system if they could be changed at run time. You could make a kernel module that changes this, but it would pretty much be a death sentence to the stability and security of the system.
What you can do and what is normally done is to open the file again with different permissions and replace the file descriptor with dup2.
3 first, since it's easiest: Can it be done in the kernel? Certainly … in theory, you can do “almost anything” there.
Both #2 and #1 come down to the question as to whether the file descriptor is re-openable.
In the most common case — the fd refers to a regular file stream in the local filesystem, the pathname of which has a directory link to which has not been altered — you can simply open the same pathname from another process. EG: If A opens /home/user/foo.log read-only, then either A or B can simply open the same pathname read-write in future.
Since you're asking, I'll assume it's not that easy. Perhaps the pathname may have been altered (eg, the file may have been unlinked), or perhaps the fd is a reference to another type of stream, like a shell pipeline, FIFO, or network socket connection.
As you probably noticed, fcntl does not allow escalation of privileges:
F_SETFL (int)
Set the file status flags to the value specified by arg. File
access mode (O_RDONLY, O_WRONLY, O_RDWR) and file creation
flags (i.e., O_CREAT, O_EXCL, O_NOCTTY, O_TRUNC) in arg are
ignored.
This is, of course, a security precaution to protect against escalation of privileges in a process that may have opened the file under temporarily elevated or changed permissions.
However, it seems that you may have a chance to open a new, duplicate stream based upon a pathname if you know the file descriptor number and the process ID of Process A.
The /proc filesystem contains virtual file entries which represent open streams. Under /proc/ pid /fd/ fd you can find a pathname to the currently-open stream. Given sufficient permissions, you can open that stream.
reader.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int
main (int argc, char** argv) {
FILE* f = fopen("/tmp/foo", "r");
while(1) {
sleep(10);
}
}
writer.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int
main (int argc, char** argv) {
/* sane PID passed in */
if (2 != argc) exit(1);
if (5 > strlen(argv[1])) exit(2);
for(size_t ci = 0; argv[ci]; ++ci) {
if (! ( ('0' <= argv[1][ci]) && (argv[1][ci] <= '9') ) ) exit(3);
}
/* note we know FD=3 so it's hard-coded */
char path[100];
int n = snprintf(path, 99, "/proc/%s/fd/3", argv[1]);
if (n < 0) exit (4);
FILE* f = fopen(path, "rw+");
fprintf(f, "written\n");
exit(0);
}
shell test
⇒ cc reader.c -o reader
⇒ cc writer.c -o writer
⇒ echo XXXXXXXXXXXX > /tmp/foo
⇒ cat /tmp/foo
XXXXXXXXXXXX
⇒ ./reader &
[1] 20709
⇒ ./writer 20709
⇒ cat /tmp/foo
written
XXXX
Here is my problem. I need to check read permissions for specific file and specific user from C code on FreeBSD. I've written a piece of code:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main(int argc, char *argv[]){
int r_ok;
if(setuid(1002)){
printf("Cant's set uid\n");
exit(1);
}
r_ok = access("/tmp/dir", R_OK);
printf("error: %d: %s\n", errno, strerror(errno));
printf("%d\n", r_ok);
return 0;
}
In general it works fine, but when I set permissions for /tmp/dir like this:
d---r-x--- 2 root fruit1 512 Sep 10 18:20 /tmp/dir
the program ouputs
error: 13: Permission denied
-1
althou user with UID 1002 is a valid member of group fruit1:
# groups 1002
orange fruit1
I will be greatfull for any help.
setuid() sets the real and effective user ID of the process, but does not modify the group access list, for that you have to call setgid(), initgroups() or setgroups().
So your program runs with the used ID 1002 and with the original group ID and group
access list, and not with the group access list of the user 1002.
That explains why the process does not have read permission to the directory.
Note that access() is considered a "security hole", (see for example access() Security Hole).
It is generally better just to try to open a file or directory instead of checking
the read permissions beforehand.
In order to protect an application from begin used wrongly, I'm trying to check that its configuration files have correct permissions, so that the application can trust the content of the files not being modified by someone else.
I believe the following rules are corrects:
the file must not be writable by others
the file must be owned by a trusted user/group: root
or
the file must be owned by the effective user/group running the application (think of setuid program)
Here an example:
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
static
int is_secure(const char *name)
{
struct stat st;
uid_t euid = geteuid();
gid_t egid = getegid();
if (stat(name, &st) != 0) {
int err = errno;
fprintf(stderr, "can't stat() '%s': %d (%s)\n", name, err, strerror(err));
return 0;
}
/* writable by other: unsecure */
if ((st.st_mode & S_IWOTH) != 0) {
return 0;
}
/* not owned by group root and not owned by effective group: unsecure */
if (st.st_gid != 0 && st.st_gid != egid) {
return 0;
}
/* not owned by user root and not owned by effective user: unsecure */
if (st.st_uid != 0 && st.st_uid != euid) {
return 0;
}
return 1;
}
int
main(int argc, char *argv[])
{
int i;
for(i = 1; i < argc; i++) {
printf("'%s' : %s\n", argv[i], is_secure(argv[i]) ? "sure" : "unsure");
}
return 0;
}
Since I'm not sure about my assumptions, can someone check if I leave some loophole in the file permissions check.
Update
sudo has a function for that: sudo_secure_path, it only check for one uid/gid, but it take care of checking for group write bit.
Regards.
Your rules and your code look correct to me, although you should be aware of the following security risks that could still affect your implementation.
An attacker with physical access to the machine or NFS/SMB access could mount the file system with a box that has root privileges, and then modify your file.
A vulnerability in another program being run as either the trusted user or root could allow that program to be exploited to modify your file.
All it would take to break your security check would be a careless user or sys-admin that messes up the privilege settings of the file. I've seen this happen during backups and copies to thumb drives, etc.
Also make sure the file is not executable. I can't think of an instance where this could be exploited on a config file, but the general rule with security is don't give any privileges that aren't required for the job.
As you can see these are not issues under control of your code. Therefore, you should make sure you client is aware of these risks before assuring them of the non-tamperability of the config file.
I believe you also want to check permissions on the directory.
A user would be able to mv another file belonging to the correct user to replace this one, if they are allowed to write to the directory.
Something like:
sudo touch foo.conf
sudo touch foo.conf-insecure-sample
mv -f foo.conf-insecure-sample foo.conf
It seems as though whatever I put as PERMS the file created has the same permissions - rwx r-x r-x
I tried 755 and 777 and the permissions just stay the same.
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<unistd.h>
#include<sys/stat.h>
#define PERMS 0777
int main(int argc, char *argv[])
{
int createDescriptor;
char fileName[15]="Filename.txt";
if ((createDescriptor = creat(fileName, PERMS )) == -1)
{
printf("Error creating %s", fileName);
exit(EXIT_FAILURE);
}
if((close(createDescriptor))==-1)
{
write(2, "Error closing file.\n", 19);
}
return 0;
}
I think you might need to change the umask before calling creat:
umask(0000);
See man 2 umask. The default umask is often 0022 which would exactly make the difference between 0777 and 0755 vanish.
Try chmod function. For more detais just right click in the IDE and type chmod.
NAME:
chmod - change mode of a file
SYNOPSIS:
#include <sys/stat.h>
int chmod(const char *path, mode_t mode);
DESCRIPTION:
The chmod() function shall change S_ISUID, S_ISGID, [XSI] S_ISVTX, and the file permission bits of the file named by the pathname pointed to by the path argument to the corresponding bits in the mode argument. The application shall ensure that the effective user ID of the process matches the owner of the file or the process has appropriate privileges in order to do this.
S_ISUID, S_ISGID, [XSI] S_ISVTX, and the file permission bits are described in <sys/stat.h>.
If the calling process does not have appropriate privileges, and if the group ID of the file does not match the effective group ID or one of the supplementary group IDs and if the file is a regular file, bit S_ISGID (set-group-ID on execution) in the file's mode shall be cleared upon successful return from chmod().
Additional implementation-defined restrictions may cause the S_ISUID and S_ISGID bits in mode to be ignored.
The effect on file descriptors for files open at the time of a call to chmod() is implementation-defined.
Upon successful completion, chmod() shall mark for update the st_ctime field of the file.
RETURN VALUE:
Upon successful completion, 0 shall be returned; otherwise, -1 shall be returned and errno set to indicate the error. If -1 is returned, no change to the file mode occurs.
More information can be found at this link.