Vladislav Shpilevoy PRO
Database C developer at Tarantool. Backend C++ developer at VirtualMinds.
Lecture 10:
Users and groups. Login. Real and effective user. Access rights for processes, files. Session. Process daemonization.
System programming
Process
Hardware
Time
File system
IPC
Network
Users
Data
structures
Virtualization
$> cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
vladislav:x:1000:1000:Vladislav,,,:/home/vladislav:/bin/bash
$> cat /etc/shadow
root:!:17704:0:99999:7:::
daemon:*:17647:0:99999:7:::
bin:*:17647:0:99999:7:::
sys:*:17647:0:99999:7:::
vladislav:$6$mVx1o/.g$vE2srpP.IWDwvUh4ozSOmmgrVaIlvi/8hCu2InOmV2G1:17704:0:99999:7:::
login : x : id : group_id : comment : home_dir : start_bin
User file
Password hash file
login : hash : update_date : min_to_change : max_valid : warn : inactive : expire
root$> useradd -m -c "comment" sysproguser
root$> tail -n 1 /etc/passwd
sysproguser:x:1001:1002:comment:/home/sysproguser:/bin/sh
root$> tail -n 1 /etc/shadow
sysproguser:!:17861:0:99999:7:::
User creation
root$> passwd sysproguser
root$> Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
root$> su vladislav
vladislav$> su sysproguser
Password:
sysproguser$>
^D
vladislav$>
Password change
root$> userdel -r sysproguser
User deletion
Kernel-space
User-space
/**
* 30.09.2018
* include/linux/cred.h
* 39 lines.
*/
struct cred {
kuid_t uid;
kgid_t gid;
kuid_t suid;
kgid_t sgid;
kuid_t euid;
kgid_t egid;
struct group_info *group_info;
};
/etc/passwd
/etc/shadow
/etc/group
...
useradd
userdel
usermod
...
There are no users in the kernel
Names, logins, passwords - all in userspace
struct task_struct {
/* ... */
const struct cred *real_cred;
const struct cred *cred;
/* ... */
};
Consequence of user-space management of users and passwords
$> touch test.txt
$> ls -l
total 0
-rw-rw-r-- 1 vladislav vladislav 0 ноя 26 18:01 test.txt
$> chown 2000 test.txt
$> ls -l
total 0
-rw-rw-r-- 1 2000 vladislav 0 ноя 26 18:01 test.txt
Can use not existing ids
$> cat /etc/group
root:x:0:
daemon:x:1:
bin:x:2:
sys:x:3:
sudo:x:27:vladislav
bluetooth:x:113:
ssh:x:115:
vladislav:x:1000:
name : x : group_id : members
$> cat /etc/gshadow
root:*::
daemon:*::
bin:*::
sys:*::
sudo:*::vladislav
bluetooth:!::
ssh:!::
vladislav:!::
name : hash : admins : members
root$> groupadd testgroup
root$> tail -n 1 /etc/group
testgroup:x:1002:
root$> useradd -m sysproguser
root$> useradd -m sysproguser2
root$> usermod -aG testgroup sysproguser
root$> usermod -aG testgroup sysproguser2
root$> tail -n 3 /etc/group
testgroup:x:1002:sysproguser,sysproguser2
sysproguser:x:1003:
sysproguser2:x:1004:
root$> groupdel testgroup
root$> tail -n 3 /etc/group
vboxsf:x:999:
sysproguser:x:1003:
sysproguser2:x:1004:
root$> userdel -r sysproguser
root$> userdel -r sysproguser2
Add new user to a group: a - append, G - groups.
Now there are 2 members in the group
Cleanup
int
main()
{
uid_t uid, euid, suid;
getresuid(&uid, &euid, &suid);
printf("uid = %d, euid = %d, "\
"suid = %d\n", (int) uid, "\
"(int) euid, (int) suid);
return 0;
}
$> gcc 4_setuid.c
$> ls -l
-rwxr-xr-x 1 vladislav vladislav a.out
$> sudo chown root ./a.out
$> ls -l
-rwxr-xr-x 1 root vladislav a.out
$> ./a.out
uid = 1000, euid = 1000, suid = 1000
$> sudo chmod u+s ./a.out
$> ls -l
-rwsr-xr-x 1 root vladislav a.out
$> ./a.out
uid = 1000, euid = 0, suid = 0
Set-user-ID and Set-group-ID bits. Executable file with these bits changes process EUID to UID of the file owner
Linux-specific function to print all 3 IDs of a process
Build an executable file. Owner and group are me
Changed owner to root
Start - didn't help. No set-user-ID bit
Added bit set-user-ID
Real UID is still 1000, but effective is 0 - (root)
uid_t uid, euid, suid;
void show_uids()
{
getresuid(&uid, &euid, &suid);
printf("uid = %d, euid = %d, suid = %d\n", (int) uid, (int) euid, (int) suid);
}
int main(int argc, const char **argv)
{
uid_t new_uid1, new_uid2, new_uid3;
new_uid1 = atoi(argv[1]), new_uid2 = atoi(argv[2]), new_uid3 = atoi(argv[3]);
show_uids();
printf("Set EUID to %d\n", (int) new_uid1);
if (seteuid(new_uid1) == -1)
printf("error = %s\n", strerror(errno));
show_uids();
printf("Set EUID to %d\n", (int) new_uid2);
if (seteuid(new_uid2) == -1)
printf("error = %s\n", strerror(errno));
show_uids();
printf("Set UID to %d\n", (int) new_uid3);
if (setuid(new_uid3) == -1)
printf("error = %s\n", strerror(errno));
show_uids();
return 0;
}
Print all UIDs
Take 3 UIDs and try to use each
Try to use as EUID
Try to set the real UID
$> gcc 5_seteuid.c
$> ./a.out 1000 0 1000
uid = 1000, euid = 1000, suid = 1000
Set EUID to 1000
uid = 1000, euid = 1000, suid = 1000
Set EUID to 0
error = Operation not permitted
uid = 1000, euid = 1000, suid = 1000
Set UID to 1000
uid = 1000, euid = 1000, suid = 1000
All UID were mine. So seteuid() to self worked fine
But can't get root
$> sudo ./a.out 1000 0 500
uid = 0, euid = 0, suid = 0
Set EUID to 1000
uid = 0, euid = 1000, suid = 0
Set EUID to 0
uid = 0, euid = 0, suid = 0
Set UID to 500
uid = 500, euid = 500, suid = 500
Try to run as root
seteuid() changed only EUID
setuid() changed all ids. Can't rollback to root from now on
Why need suid?
$> sudo chown root ./a.out
$> sudo chmod u+s ./a.out
$> ls -l
-rwsr-xr-x 1 root vladislav a.out
$> ./a.out 1000 0 1000
uid = 1000, euid = 0, suid = 0
Set EUID to 1000
uid = 1000, euid = 1000, suid = 0
Set EUID to 0
uid = 1000, euid = 0, suid = 0
Set UID to 1000
uid = 1000, euid = 1000, suid = 1000
Let UID initially be non-root, and EUID be root. This can be done via set-user-ID bit
SUID saves previous EUID value, from where it can be restored later
void
seteuid(uid_t new_euid)
{
if (euid != 0 && new_euid != euid &&
new_euid != suid && new_euid != uid)
error();
if (new_euid != euid) {
suid = euid;
euid = new_euid;
}
}
void
setuid(uid_t new_uid)
{
if (euid == 0)
uid = new_uid;
seteuid(new_uid);
}
All the same works for GID - Group ID. GID also has a 'saved' value - SGID.
Access rights for owner, group, and others
r | w | x | |
---|---|---|---|
File | Read | Write | Exec |
Directory | Get file list | Delete/add/rename files | Search file by name known in advance |
owner group rwx rwx rwx
set-user-ID | set-group-ID | sticky | |
---|---|---|---|
Executable file | Set EUID to owner's UID when executed | Set EGID to owner's GID when executed | Long time ago it was used to keep the file's .text section in the main memory even after termination. Now means nothing |
Ordinary file | - | - | - |
Directory | - | All new content will have the same group as this folder | Delete and rename is allowed only for own files |
$> ls -l
total 36
drwxrwxr-x 2 vladislav vladislav 4096 ноя 27 04:46 .
drwxr-xr-x 16 vladislav vladislav 4096 ноя 27 04:15 ..
-rwxr-x--- 1 vladislav vladislav 230 ноя 27 03:08 4_setuid.c
-rwxr-x--- 1 vladislav vladislav 848 ноя 27 04:40 5_seteuid.c
-rwxr-x--- 1 vladislav vladislav 660 ноя 27 04:45 6_seteuid_twice.c
-rwsr-xr-x 1 root vladislav 8664 ноя 27 04:46 a.out
-rw-rw-r-- 1 vladislav vladislav 16 ноя 27 02:35 test.c
-rw-rw-r-- 1 2000 vladislav 0 ноя 26 18:01 test.txt
$> mkfifo myfifo
$> ls -l myfifo
prw-r--r-- 1 vladislav vladislav 0 ноя 27 05:56 myfifo
$> nc -l -U ./socket.s
^C
$> ls -l socket.s
srwxr-xr-x 1 vladislav vladislav 0 ноя 27 06:00 socket.s
Access mask: 3 by 3 bits
File type
Owner
Group
$> mkdir testdir
$> ls -l testdir
drwxr-xr-x 2 vladislav vladislav 4096 ноя 27 06:06 testdir
$> chmod g+s testdir
$> ls -l testdir
drwxr-sr-x 2 vladislav vladislav 4096 ноя 27 06:06 testdir
$> chmod o+t testdir
$> ls -l testdir
drwxr-sr-t 2 vladislav vladislav 4096 ноя 27 06:06 testdir
$> ls -ld /tmp
drwxrwxrwt 13 root root 4096 ноя 27 06:09 /tmp
Enable set-group-ID bit
Enable sticky bit
Sticky folder - /tmp
S_IRWXU 00700 user rwx
S_IRUSR 00400 user r--
S_IWUSR 00200 user -w-
S_IXUSR 00100 user --x
S_IRWXG 00070 group rwx
S_IRGRP 00040 group r--
S_IWGRP 00020 group -w-
S_IXGRP 00010 group --x
S_IRWXO 00007 others rwx
S_IROTH 00004 others r--
S_IWOTH 00002 others -w-
S_IXOTH 00001 others --x
S_ISUID 0004000 set-user-ID bit
S_ISGID 0002000 set-group-ID bit
S_ISVTX 0001000 sticky bit
int
open(const char *pathname, int flags, mode_t mode);
sem_t *
sem_open(const char *name, int oflag,
mode_t mode, unsigned int value);
int
mkfifo(const char *pathname, mode_t mode);
S_IRUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXOTH = r-xrw---x
int
main()
{
int fd = open("tmp.txt",
O_CREAT | O_RDWR,
S_IRUSR | S_IXUSR);
if (fd == -1) {
printf("error = %s\n",
strerror(errno));
} else {
close(fd);
}
return 0;
}
$> gcc 6_access_mask.c
$> ./a.out
$> ls -l tmp.txt
-r-x------ vladislav vladislav tmp.txt
$> echo 123 > tmp.txt
-bash: tmp.txt: Permission denied
$> cat tmp.txt
$>
Access rights for new resources subtract umask before being applied. Umask - also 9 bits, with the same meaning. But evidently if a mask has i-th bit 1, then in all new resources the i-th bit will be 0 regardless of what was requested
int
main()
{
mode_t mode = S_IRWXU | S_IRWXO |
S_IRWXG;
int fd = open("tmp.txt",
O_CREAT | O_RDWR,
mode);
if (fd == -1) {
printf("error = %s\n",
strerror(errno));
} else {
close(fd);
}
return 0;
}
$> umask
0022
$> gcc 7_umask.c
$> ./a.out
$> ls -l tmp.txt
-rwxr-xr-x vladislav vladislav tmp.txt
S_IRWXU | S_IRWXO | S_IRWXG = rwxrwxrwx
(S_IRWXU | S_IRWXO | S_IRWXG) & ~umask = rwxr-xr-x
Octal notation
Process:
Resource:
Access change
int
chmod(const char *pathname, mode_t mode);
int
fchmod(int fd, mode_t mode);
Check access rights
int
stat(const char *pathname,
struct stat *statbuf);
int
fstat(int fd, struct stat *statbuf);
struct stat {
/* ... */
mode_t st_mode;
/* ... */
};
Check *ID of the process
uid_t getuid(void);
uid_t geteuid(void);
int setuid(uid_t uid);
int seteuid(uid_t euid);
gid_t getgid(void);
gid_t getegid(void);
int setgid(gid_t gid);
int setegid(gid_t egid);
Name <-> ID
struct passwd *getpwnam(const char *name);
struct passwd *getpwuid(uid_t uid);
struct group *getgrnam(const char *name);
struct group *getgrgid(gid_t gid);
int
setpgid(pid_t pid, pid_t pgid);
pid_t
getpgid(pid_t pid);
pid_t
getpgrp(void);
int
setpgrp(void);
int
main(int argc, const char **argv)
{
pid_t id = getpgrp();
pid_t pid = getpid();
int i = 0;
if (argv[1][0] == 'w')
printf("1\n");
else
scanf("%d", &i);
fprintf(stderr, "group = %d, "\
"pid = %d\n", (int) id,
(int) pid);
return 0;
}
$> gcc 8_pgrp.c
$> ./a.out write | ./a.out read
group = 97406, pid = 97406
group = 97406, pid = 97407
proc1
proc2
proc3
proc4
proc5
shell
Session leader
Background process groups
Foreground process group
proc6
proc7
Signal about the terminal death
Signals SIGINT, SIGQUIT. Terminal input
pid_t
setsid(void);
Creates a new session with one group. The invoker process becomes a leader of the session and the group.
What is so hard about daemon creation?
int daemonize(const char *log_file)
{
if (fork() > 0)
exit(0);
int fd = open(log_file, O_CREAT |
O_WRONLY | O_TRUNC,
S_IRWXU);
dup2(fd, STDOUT_FILENO);
close(fd);
close(STDIN_FILENO);
close(STDERR_FILENO);
return setsid();
}
int main(int argc, const char **argv)
{
daemonize(argv[1]);
int server = socket(AF_INET, SOCK_STREAM,
IPPROTO_TCP);
/* ... */
Daemonize the server from previous lectures
Fork() creates a child which is guaranteed not to be a group leader
New process will be disconnected from the terminal - the standard streams should be closed or replaced
setsid() creates a new session and group, makes this process their leader, and leaves the terminal in the old session
It is enough to invoke the daemonizer to create a daemon process and kill the current
$> gcc 9_daemon.c
$> ./a.out log.txt
$> ps aux | grep a.out
v.shpilevoy 97920 0:00.00 grep a.out
v.shpilevoy 97914 0:00.00 ./a.out log.txt
$> ps -fp 97914
UID PID PPID C STIME TTY CMD
502 97914 1 0 11:31 ?? ./a.out log.txt
$> gcc 9_client.c
$> ./a.out
300
Sent 300
Received 301
^D
$> cat log.txt
New client
Interact with fd 2
Received 300
Sent 301
Interact with fd 2
Client disconnected
$> kill -9 97914
Users and groups are all in userspace. The kernel has no idea about registrations, passwords, logins, names. It only checks IDs against each other and access types against access masks.
Real user - who started process. Effective user - on whose behalf the process works. Those IDs can give temporary downgrade or root privileges, depending on usage. All access checks use your effective user id.
Meaning of the 9 bits (in fact more than 9) in the access bitmask depends on the resource type. Directory, file, socket, symlink, fifo, etc.
Processes are organised into groups and sessions. The important part is that leaving parent's group and session turns the process into a background daemon working without a console.
Lectures: slides.com/gerold103/decks/sysprog_eng
Press on the heart, if like the lecture
By Vladislav Shpilevoy
Users and groups, rights. Attributes and access rights of files and processes. Process groups, sessions. /etc/passwd, /etc/group. Sticky bit. Process daemonization. Object attributes.
Database C developer at Tarantool. Backend C++ developer at VirtualMinds.