Lecture 10:

Users and groups. Login. Real and effective user. Access rights for processes, files. Session. Process daemonization.

System programming

Education

Lecture plan

  • Users and groups place in Linux
  • Real and effective user
  • Resource access bitmask
  • Access mask manipulations
  • Process group
  • Session
  • Process daemonization

Linux kernel

Process

Hardware

Time

File system

IPC

Network

Users

Data

structures

Virtualization

Users and groups [1]

$> 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

Users and groups [2]

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

Users and groups [3]

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;
        /* ... */
};

Users and groups [4]

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

Users and groups [5]

$> 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

Users and groups [6]

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

Real and effective user [1]

  • Real - who launched the process
  • Effective - on whose behalf the process works

Real and effective user [2]

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)

Setuid() and seteuid() [1]

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

Setuid() and seteuid() [2]

$> 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

Setuid() and seteuid() [3]

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.

All IDs of a process

  • UID, GID
  • EUID, EGID
  • SUID, SGID
  • Additional groups ...

Resource access rights [1]

  • Resource: file, device, UNIX socket, IPC
  • Access attributes: user, group, access mask (9 bit), set-user-ID, set-group-ID, sticky bit

Resource access rights [2]

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

Resource access rights [3]

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

Resource access rights [4]

$> 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

Resource access rights [5]

$> 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

Resource access rights [6]

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

Resource access rights [7]

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

$>

Resource access rights [8]

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

Processes and resources

Process:

  • UID, GID,
  • EUID, EGID,
  • SUID, SGID
  • Additional groups ...

Resource:

  • Owner, group
  • 9 bit access mask for owner, group, and others
  • set-user-ID, set-group-ID, sticky

Access check

  1. If EUID = 0, any access is allowed
  2. If EUID = UID of owner, then access type is checked by the first 3 bits
  3. If EGID or any additional GID of the process == GID of a resource, access type is checked by the second 3 bits
  4. In all other cases the last 3 bits are used to check the access type

Useful functions

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);

Process group [1]

  • Processes can be joined into a group
  • Is used by console for group signal sending, termination
int
setpgid(pid_t pid, pid_t pgid);

pid_t
getpgid(pid_t pid);

pid_t
getpgrp(void);

int
setpgrp(void);

Process group [2]

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

Session [1]

  • Process groups live in sessions
  • Session can have a leader and a controlling terminal ('PTY')
  • Session can have one foreground process group, and many background groups
  • Terminal broadcasts signals to the foreground group - SIGINT, SIGQUIT

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

Session [2]

pid_t
setsid(void);

Creates a new session with one group. The invoker process becomes a leader of the session and the group.

  • Does not work if the process is already a group leader
  • The new session does not have a terminal

Process daemonization [1]

What is so hard about daemon creation?

  • Need to exit the foreground group. Otherwise will be killed by SIGINT, SIGQUIT eventually
  • Need to exit the session so as to detach from the terminal

Process daemonization [2]

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

Process daemonization [3]

$> 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

Summary

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.

Conclusion


Press on the heart, if like the lecture

System programming 10

By Vladislav Shpilevoy

System programming 10

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.

  • 1,558