Checking file access permissions for specified user in C / *nix - c

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.

Related

setuid() return -1 when specified user ID is the same as the effective user ID

I've compiled a binary Authorization and here's its permission
-rwsr-sr-x 1 root wheel 18464 10 26 22:07 ./Authorization
I ran it with the root permission
sudo ./Authorization
so in the beginning, the uid(real uid) and euid(effective uid) of my process is
uid:0 euid:0
then my program would invoke seteuid(501) to change the euid, now it's
uid:0 euid:501
At last, my program would invoke setuid(501), I expected the result is
uid:501 euid: 501
According to manual of of setuid()
The setuid() function is permitted if the effective user ID is that of the super
user, or if the specified user ID is the same as the effective user ID.
However, setuid(501) return -1 which is not expected, and not the behavior described in the manual, WHY??
Here's my code
#include <stdio.h>
#include <unistd.h>
int main(int argc, const char * argv[]) {
printf("uid: %d euid: %d\n", getuid(), geteuid());
if (seteuid(501) == -1) {
printf("seteuid error\n");
}
printf("seteuid(501)> uid: %d euid: %d\n", getuid(), geteuid());
if (setuid(501) == -1) {
printf("setuid error\n");
}
printf("setuid(501)> uid: %d euid: %d\n", getuid(), geteuid());
return 0;
}
You can try to figure it out using the classic error explanation.
To do that you can add some code to the section:
if (setuid(501) == -1) {
printf("setuid error\n");
printf ("Error while setting uid: %s\n", strerror(errno));
}
Dont forget to add these headers:
#include <string.h>
#include <errno.h>
Good luck!
I tried your code on my macOS (10.12.6) and the error is EPERM (Operation not permitted).
Not sure if the macOS's man page is out of date. On my macOS, the setuid's man page is still of June 4, 1993.
On Linux (Debian 8.9), setuid's man page says:
setuid() sets the effective user ID of the calling process. If the effective UID of the caller is root, the real UID and saved set-user-ID are also set.
The APUE (2nd edition) book (8.11 Changing User IDs and Group IDs) says:
Only a superuser process can change the real user ID. Normally, the real user ID is set by the login(1) program when we log in and never changes. Because login is a superuser process, it sets all three user IDs when it calls setuid.
The POSIX.1-2008 (SUSv4) says:
The various behaviors of the setuid() and setgid() functions when called by non-privileged
processes reflect the behavior of different historical implementations. For portability, it is
recommended that new non-privileged applications use the seteuid() and setegid() functions instead.

How to ensure correct file permissions

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

create file and assign permissions

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.

Getting the environment of a process running under a different user

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.

Using chmod in a C program

I have a program where I need to set the permissions of a file (say /home/hello.t) using chmod and I have to read the permissions to be set from a file. For this I first read the permissions into a character array and then try to modify the permissions of the file. But I see that permissions are set in a weird manner.
A sample program I have written:
main()
{
char mode[4]="0777";
char buf[100]="/home/hello.t";
int i;
i = atoi(mode);
if (chmod (buf,i) < 0)
printf("error in chmod");
}
I see that the permissions of the file are not set to 777. Can you please help me out on how to set the permissions of the file after reading the same from a character array.
The atoi() function only translates decimal, not octal.
For octal conversion, use strtol() (or, as Chris Jester-Young points out, strtoul() - though the valid sizes of file permission modes for Unix all fit within 16 bits, and so will never produce a negative long anyway) with either 0 or 8 as the base. Actually, in this context, specifying 8 is best. It allows people to write 777 and get the correct octal value. With a base of 0 specified, the string 777 is decimal (again).
Additionally:
Do not use 'implicit int' return type for main(); be explicit as required by C99 and use int main(void) or int main(int argc, char **argv).
Do not play with chopping trailing nulls off your string.
char mode[4] = "0777";
This prevents C from storing a terminal null - bad! Use:
char mode[] = "0777";
This allocates the 5 bytes needed to store the string with a null terminator.
Report errors on stderr, not stdout.
Report errors with a newline at the end.
It is good practice to include the program name and file name in the error message, and also (as CJY pointed out) to include the system error number and the corresponding string in the output. That requires the <string.h> header (for strerror()) and <errno.h> for errno. Additionally, the exit status of the program should indicate failure when the chmod() operation fails.
Putting all the changes together yields:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
int main(int argc, char **argv)
{
char mode[] = "0777";
char buf[100] = "/home/hello.t";
int i;
i = strtol(mode, 0, 8);
if (chmod (buf,i) < 0)
{
fprintf(stderr, "%s: error in chmod(%s, %s) - %d (%s)\n",
argv[0], buf, mode, errno, strerror(errno));
exit(1);
}
return(0);
}
Be careful with errno; it can change when functions are called. It is safe enough here, but in many scenarios, it is a good idea to capture errno into a local variable and use the local variable in printing operations, etc.
Note too that the code does no error checking on the result of strtol(). In this context, it is safe enough; if the user supplied the value, it would be a bad idea to trust them to get it right.
One last comment: generally, you should not use 777 permission on files (or directories). For files, it means that you don't mind who gets to modify your executable program, or how. This is usually not the case; you do care (or should care) who modifies your programs. Generally, don't make data files executable at all; when files are executable, do not give public write access and look askance at group write access. For directories, public write permission means you do not mind who removes any of the files in the directory (or adds files). Again, occasionally, this may be the correct permission setting to use, but it is very seldom correct. (For directories, it is usually a good idea to use the 'sticky bit' too: 1777 permission is what is typically used on /tmp, for example - but not on MacOS X.)

Resources