November 11, 2015 - Me vs. Groups

November 11, 2015

Me vs. Groups

Ever changed your user's groups and wondered why it didn't have any effect until you logged out? I find nothing more frustrating than when logging out or shutting down fixes a problem, it's an indication that something happened that I don't understand and couldn't control, and if I can't control something, I can't claim ownership, therefore, logging out to solve a problem is essentially theft, and I won't stand for that.

So I did some research and some coding. Searching for information about how groups get set is a frustrating exercise. I found countless articles and forum posts saying "you need to log out" or even "a reboot is required", but mixed in with all that there was some good information, but never the whole picture. To piece together the remainder of the Linux groups puzzle, I turned to the Linux man pages and kernel sources. I also found some good information in the busybox ash source code. It turns out that groups (and users for that matter) work a little like environment variables.

Each process has a user and group associated with it (maintained by the Linux kernel). When a program forks and creates a child, the child inherits its group and user. It is possible for programs to modify their user (e.g. su, sudo, login, etc.) and group through system calls like SYS_setgroups. However, Not all programs are allowed to call these system calls, the program needs to have permission, otherwise the kernel will reject the request and return an error. The kernel identifies allowed programs with the CAP_SETGID capability. Programs with the setuid and setgid bit set should have this capability (except under certain circumstances, like if run from a filesystem mounted with nosetuid flag turned on). Programs run as root also get this capability.
In most Linux distros, very few programs are set up with this capability, and the shell is most certainly not one of them. This means that when you modify groups (either by command line tools or by editing /etc/groups), you aren't actually changing anything. Think of it just like you would editing any other configuration file. If your command line tool added a group, the only way it can do so permanently is by adding it to /etc/groups. Even if it were to actually call setgroups with th proper permissions, it would only change it's own group, and its parent's group (the shell) would remain completely unchanged. Since, the shell doesn't have the capability to change its own groups, there is no way for it to update even if it detects a change, and when it starts a new program, all it can do is pass on what it already has. Logging out and back in solves this problem because login does have this capability, and can read /etc/groups and set its own groups before forking and creating children.

To illistrate this all, I wrote a program that calls setgroups and then forks and execs a program. If you use it to exec a program that shows its groups, you can see the changes it made, even though /etc/groups will remain unchanged, and no other process will be affected.
Here is the code:
section .data
	title:  db "Drop Groups v0.1",10,0
	author: db "written by Philip Rushik",10,10,0

section .text
	global _start

	_start:
		xor rbp,rbp 		;AMD64 ABI Requirement
		pop rdi 			;Get argument count
		mov rsi,rsp 		;Get argument array
		and rsp,-16 		;Align stack pointer

		push rdi 			;store arg count
		push rsi 			;store arg address

		mov rax,1 			;write
		mov rdi,1 			;stdout
		mov rsi,title 		;text to write
		mov rdx,17 			;size
		syscall 			;do it

		mov rax,1 			;write
		mov rdi,1			;stdout
		mov rsi,author		;text to write
		mov rdx,26 			;size
		syscall 			;do it

		mov rax,116 		;setgroups
		mov rdi,0 			;size = 0
		lea rsi,0 			;groups = null
		syscall

		mov rax,57 			;fork - maybe vfork would be better
		syscall

		cmp rax,0 			;compare rax with 0
		jnz parent 			;if not 0, wait for child

	child:
		mov rax,59 			;execve
		mov rsi,rsp 		;args
		add rsi,24 			;adjust this to be the right place
		mov rdi,[rsi] 		;use agv[1] as prog
		add rdi,8 			;this is arg[1]
		add rsi,8			;arg[1] is also the program to run
		mov rdx,0 			;nothing
		syscall

	jmp exit 				;don't wait by accident
	parent:
		push rax 			;save the pid
		mov rax,61 			;wait4
		pop rdi 			;get back the pid
		mov rsi,0 			;status - null (termination only)
		mov rdx,0 			;0 - no options
		mov r10,0 			;null
		syscall

	exit:
		mov rax,60 			;exit
		syscall

		ret					;don't get here

Now you know, Linux groups are inherited from the parent. That's all for now. Peace!