Major: a basic sudo like application
Audience
If you want to have a good understanding of how process permission are handle in Linux/unix and want to understand the basic principle behind sudo application, how it works behind the scenes, this article would be helpful for you.
Motivation
Implementing the sudo like application and writing this article is part of a process and goal that i set to myself to reduce the amount of unknown known and unknown unknown. I believe that there is a certain level in software development/computer science, that to be achieved, we must truly have a modest level of understand of Linux/Unix. In my experience, Linux/Unix has proven to be developer-friendly, you might not agree with me and i understand that but this is what i believed. That being said lets move forward.
What is sudo ?
As they github repository says, sudo is:
Utility to execute a command as another user
That was somehow a revelation for me, because for many years using linux( very dummy use of it) i thought that sudo use was limited to privilege escalation
simply putting, the sudo is a tool used to manipulate process's users ID, by setting the process's users ID to values other than those inherited by the parent process . This allow for example to a process get temporary root permission to executed task that required privileged mode and drop those permission when not need by change to a non privileged user ID.
To be able to do this, one of the condition is that, the sudo application must have a setuid() permission bit set on the sudo executable file. We will see later when implementing major, the name i gave to the application that implements the basic sudo like application.
What sudo does when run, it it check a file called sudoers which resides at /etc/sudoers, this file contains a set of security policy
Sudoers file use the EBNF to set its instruction, bellow a truncated example of instruction in sudoers file:
# Ditto for GPG agen
#Defaults:%sudo env_keep += "GPG_AGENT_INFO"
# Host alias specification
# User alias specification
# Cmnd alias specification
# User privilege specification
root ALL=(ALL:ALL) ALL
# Members of the admin group may gain root privileges
%admin ALL=(ALL) ALL
# Allow members of group sudo to execute any command
%sudo ALL=(ALL:ALL) ALL
# See sudoers(5) for more information on "@include" directives:
@includedir /etc/sudoers.d
In the above snippet we have a set of security policy define and one of them is:
# User privilege specification
root ALL=(ALL:ALL) ALL
What this means is, the root user as privilege to run any command (the last ALL) as any user(second ALL) and group(third ALL) combination in any host (the first ALL). The user privilege specification is set using the following rule:
who where = (as_whom) what
Technical speaking, sudo does not need to have the sudoers file to run a command as another user, it just need to have the setuid permission set and the sudo executable file should have root as its owner. But to enforce more controlled security mechanism the sudo does not just go and run the application as the user id specified in its -u argument, before that, it goes and check its security policy to verify if the user has permission to execute the command or not by parsing the sudoers file and taking the correct action after this process. Without that, any user would be able to run any command as root and that would be dangerous for the operating system.
Background
Before we dive in to major, the sudo like implementation, lets understand how linux/unix manages process's permissions. In linux/unix, a process is associated with four users ID, those are:
All these user data are inherited from the parent process when a process is spawned or forked.
The real user id represents the ID of the user that started or forks the process, the saved user ID, represents the value of original effective user ID, this user ID inherit the value of the parent when the process forks but is set to the value of the effective user ID when exec system call runs, this value can be used by the effective user ID later on.
The filesystem user ID are used when dealing with filesystem permission.
The later but not least is the effective user ID, this user ID represents the user ID that is used to check the process permission during its execution, its against this value that the kernel check if the process has permission to do some task or not. During the fork, the value of effective user ID is inherited from the parent and during the exec system call, this value normally don't changes, however, the value of the effective user ID is set to the user ID associated with the application program file during the exec when the application program file has setuid permission bit set.
There is something that is important to note, non privileged user can change the effective user id to either real user ID or saved user ID and noting more, while privileged user ID like root, can change the effective user ID to any value.
For real user ID and saved user ID non privilege user may not have permission to change them, root user can change the real user ID to any value and can change the value of saved user ID to the value of real user ID only.
How it works
As i said before, we will implement a tool to do the basic sudo task, the tool will be called major. To allow major application to be able to start command with privilege mode even if the current user ID spawning the process doesn't have it, the major application must meet the following condition:
The need of be owning by root user is self explanatory because if you need to run as root, the file must be owning by the root.
The need of setuid permission bit may need more explanation. The setuid permission bit is what makes possible to set the value of effective user ID to the user ID that own the application program file during the exec.
Recommended by LinkedIn
This is the behavior that we get from sudo utility when we don't set the -u parameter, as the sudo file has setuid bit set and the owner of the file is root, so when the sudo process emit a exec system call, the effective user ID is automatically set to root (sudo program file owner) then giving the sudo privilege to executed any task, has the user ID associated with the sudo process is root (user ID = 0).Now sudo can do any thing in our operating system like open file, device that would require root privilege.
Well, now sudo can do any thing, but what we want is to allow any command that is start from sudo to have same root power, this is how it happens:
When you perform the following command from shell terminal:
> sudo cat /ect/sudoers
After all process finishes, both sudo and cat can terminate.
Sudo not only accept to run command as root, it can run as any specified user. When a user is specified using -u argument, before sudo forks itself, it changes its process user ID to the one emitted by the user. This change is made by sending a setuid(user_id) and seteuid (user_id) system calls
This is basically how sudo is able to make others command to run as root and other users. This is what major will basically do, run any command as root.
To continue, we say that for sudo like tools be able to run command as root, we must have permission bit set and the sudo executable must be owned by the root. We can meet the above required condition by executing the following shell script (Bash):
# In order to run as root the program file must belong to root use
> sudo chown root:root ./major
# To be able to make the the executable file owner as process user's ID, the executable file must have the setuid bit permission set
> sudo chmod u+s ./major
It may seems contra-intuitive to use sudo as a way to allow another application to do what the sudo itself does, and the question that may arise can be: what if the sudo command did not exist, how could we achieve this? The answer for this question is that we can use the su command to gain root privilege and all command executed will be run under root user but this can create security flaws, that is why we should not use it for longer sessions.
> su -l
# In order to run as root the program file must belong to root use
> chown root:root ./major
# To be able to make setuid syscall the program file must s in the permission byte
> chmod u+s ./major
Implementation
The sudo util is compose by so many aspect that we are not implement here, the scope of this is to implement the basic privilege escalation that sudo provides, which means we will implement a tool that will allow you to run task that need root privilege.
Lets understand how the major is started. As a cli we will start the major through a shell tool.
./major cat /etc/sudoers
What happens behind the scenes, is that major app forks itself and create a child with almost same characteristic and then, emit a exec system call to load the binary and its argument in the created child process. The following is what is passed to exec command:
cat /etc/sudoers
This means that the child process with executed the cat application with his respective parameter /etc/sudoers. We choose /etc/sudoers because it is owned by the root and only process with root user id can read from it.
Golang makes it easy to run the fork and exec system call by allowing us to do it in just one function call like cmd.Run(), cmd.Start(), cmd.Output() from exec package.
//setup the command to executed and it arguments
cmd := exec.Command(args[1], args[2:]...);
//Run the binary/command wait it finish an then get the content sent to stdout by the binary in the exec syscall
out, errr:= cmd.Output();
This simple implementation is due to the abstractions that the exec package provide. If you would like more low level implementation you could use function from syscall and os package.
After the execution of the child, we will get what the child process send to stdout and store it to parent process (major) variable which then send it to stdout again where we can see the output in terminal emulator.
fmt.Println(string(out))
Quite simple implementation for so much complex theory we describe early in this article right? That is the power of abstraction.
The complete code implementation of major can be found in:
Summary
The journey to learn about all this stuff and write the application and this article was great and required lot of effort that in the end compensate.
Knowing the basic of how one of most used linux application works and all evolved parts like process user's id, system calls opened many other curiosity about how linux works in me and i hope this happed to you too.
Hope that was helpful in some extends.
Now this is something really interesting. Reminds me of one of my unfinished Linux projects. Was surprised to see you using GoLang for it. I went with Cold blood C 😅. Keep it up Ornélio Chaúque.