The whole framework of the previous lesson — subjects, domains, access matrix, least privilege — rests on a word we have not yet defined: identity. A protection domain is assigned to a subject, and the subject is identified by a number. The Linux kernel does not know meteora, or nuria, or carlos: it knows 990, 1002 and 1001. Everything else — the name, the password, the group, the shell — lives in text files under /etc that the kernel does not even read.
That has a consequence worth internalizing from the first minute: identity in UNIX is a userspace convention resting on a number. If you delete meteora's line from /etc/passwd, the 17,280,000 bytes of 2026-08-31.dat still belong to UID 990 and ls -l shows them as 990. If you create another user with 990, that user is the owner of Meteora's data without having touched a single file. The number is the identity; the name is a label.
This lesson walks the complete chain: the identifiers and why every process carries three UIDs — which fully explains the setuid passwd of 04-06; the three /etc files field by field; the lifecycle of an account and the moment that produces the most incidents, offboarding; authentication itself, with how a password is stored, what the $y$j9T$... of /etc/shadow means and which attacks threaten it — described at a conceptual level, so you can defend against them; PAM, the piece that decides whether you get in and that almost nobody understands until they break it; SSH public key authentication, which is what you will really use on meteo-01; and controlled elevation with sudo, with the defensive catalog of the configuration mistakes that turn a normal account into root.
Contents
- UID, GID and the three identifiers of a process
- Supplementary groups and reading
id,whoamiandgroups /etc/passwd,/etc/shadowand/etc/groupfield by field- System accounts versus human accounts
- Managing an account and its lifecycle
- Authentication: the three factors
- How a password is stored
- Attacks on credentials and their countermeasures
- PAM: the architecture that decides whether you get in
- Public key authentication with SSH
- Controlled privilege elevation:
suandsudo - Privilege escalation: defensive catalog and auditing
UID, GID and the three identifiers of a process
Every process carries a set of numeric identifiers that the kernel consults on every access check. There are three user ones, and that multiplicity is not a historical whim: it solves a concrete problem.
| Identifier | Abbreviation | What it is for |
|---|---|---|
| Real UID | ruid |
Who you are: who launched the process. Accounting, signals, auditing |
| Effective UID | euid |
What you can do: it is what the kernel checks on every access |
| Saved UID | suid |
What you could get back: a copy that allows dropping privilege and raising it again |
Bring back the setuid passwd of 04-06 and it will now make complete sense. User joan (UID 1000) runs it:
Before the execve: ruid=1000 euid=1000 suid=1000
After the execve: ruid=1000 euid=0 suid=0
↑ ↑
who launched it privilege it
(for auditing) acts withThe program needs both things at once: euid=0 to write to /etc/shadow, and ruid=1000 to know whose password it should change. If only the effective one existed, passwd would have no reliable way of knowing who invoked it and anyone could change anyone's password. The three identifiers exist, in short, so that authority and identity can be different and both remain available.
The saved one enables the most important pattern for writing secure services: dropping privilege.
/* The ingestor opens the privileged socket and drops to UID 990 forever */
open_station_socket(); /* needs privilege */
if (setgroups(0, NULL) != 0) return 1; /* CRITICAL ORDER: */
if (setgid(990) != 0) return 1; /* groups → GID → UID */
if (setuid(990) != 0) return 1;
if (setuid(0) == 0) { fprintf(stderr, "FATAL: privilege is recoverable\n"); return 1; }
main_loop(); /* now unprivileged */Three details on which real vulnerabilities depend. The order is mandatory: setgroups and setgid go before setuid, because once UID 0 is gone there is no permission left to change the groups and the process would keep root's supplementary groups. setuid() called by root changes all three identifiers at once, and that is why the drop is irreversible; with seteuid() the saved one would still be 0 and an attacker would get root back with a single call. And the final check is not paranoia: it verifies that the drop was definitive, and its absence has caused documented escalations in very well-known software. The same applies to the GIDs, and the setgid bit acts on the effective GID just as setuid does on its own.
Supplementary groups and reading id, whoami and groups
A process belongs to one primary group and to several supplementary ones, and for the checks of 04-06 any of them will do to enter the group class.
$ id
uid=1001(carlos) gid=1001(carlos) groups=1001(carlos),4(adm),27(sudo),990(meteora)
$ whoami # equivalent to `id -un`: the name of the EFFECTIVE UID
carlos
$ id meteora # query another identity without being it
uid=990(meteora) gid=990(meteora) groups=990(meteora)uid=1001(carlos) is the effective UID with its name resolved; gid=1001 is the primary group, assigned by default to the files he creates — unless the directory is setgid, like /var/lib/meteora; and the groups= list is all of his groups. Carlos's three groups are three distinct security decisions, and it is good that they are: adm gives him read access to the logs, sudo enables elevation and meteora gives him access to the service's group. It is the separation of privilege of 05-01 applied to a person.
A detail that surprises people: whoami reports the effective UID, not the real one. Inside a setuid root process it says root even though joan launched it. To find out who is behind it you use id -ru or, in an interactive session, who am i, which queries the session records and survives chained su calls.
Supplementary groups are computed at login and inherited across
fork. Ausermod -aG meteora carlosdoes not affect open sessions or already-started processes.idwill show the new group because it re-reads/etc/group, but the process will still lack the access: the kernel uses the credentials the process carries, not the file. You have to restart the session or the service.
The quick way to tell them apart: id reads the file, and grep Groups /proc/<pid>/status shows the process's real groups. If they differ, that is your problem.
/etc/passwd, /etc/shadow and /etc/group field by field
/etc/passwd: seven colon-separated fields
root:x:0:0:root:/root:/bin/bash carlos:x:1001:1001:Carlos Ruiz,Systems,,:/home/carlos:/bin/bash meteora:x:990:990:Meteora Service,,,:/var/lib/meteora:/usr/sbin/nologin
| # | Field | Value for meteora |
What it means |
|---|---|---|---|
| 1 | Name | meteora |
The login name. Unique |
| 2 | Password | x |
Historical placeholder: the hash is in /etc/shadow |
| 3 | UID | 990 |
The real identity as far as the kernel is concerned |
| 4 | Primary GID | 990 |
Default group for the files it creates |
| 5 | GECOS | Meteora Service,,, |
Free-form field: name, office, phone numbers |
| 6 | Directory | /var/lib/meteora |
The account's $HOME |
| 7 | Shell | /usr/sbin/nologin |
Program run at login |
The x in field 2 is living history: until the early nineties the hash was right there, in a world-readable file, because many programs need to translate UIDs into names. When computing power made dictionary attacks feasible, the hashes were moved to /etc/shadow, readable only by root, and the x remained as a marker. It is a perfect example of surface reduction: the sensitive datum is taken out of the file everyone needs to read.
Field 7, /usr/sbin/nologin, is a real program that prints a notice and exits with an error; the difference from /bin/false is cosmetic, but leaving the field empty does matter, because then /bin/sh is used.
Why
meteorahas no shell. It is least privilege applied to identity: the account exists only to own some files and to be the identity of some processes started by systemd. If an attacker obtains ameteoracredential, they cannot use it to get in over SSH or to open a session: it is only useful to them if they are already inside. A whole layer of defense for one word in field 7.
/etc/shadow: nine fields, and the one that matters is the second
| # | Field | Example | Meaning |
|---|---|---|---|
| 1 | Name | carlos |
Links to /etc/passwd |
| 2 | Hash | $y$j9T$... |
The derived password (section 7) |
| 3 | Last change | 20330 |
Days since 1/1/1970 when it was changed |
| 4 | Minimum days | 1 |
Cannot change it again for 1 day |
| 5 | Maximum days | 365 |
Expires after a year |
| 6 | Warning | 14 |
Warns 14 days before expiry |
| 7 | Inactivity | 30 |
Grace days after expiry before locking |
| 8 | Expiry | (empty) | Absolute expiration date of the account |
| 9 | Reserved | (empty) | Unused |
The four possible values of field 2 are constantly confused: $y$... is a valid password; ! or !hash is a locked account (passwd -l), a reversible lock because the hash is kept behind it and passwd -u restores it; * means it has never had a password and never will, the normal state for package accounts; and empty means access with no credential, which is an emergency. That is why meteora:!: is the right thing for a service account.
And a decisive nuance for section 5: locking the password does not lock SSH key access, because they are different authentication mechanisms.
/etc/group: four fields
Name, password placeholder, GID and list of supplementary members. The subtlety: members whose primary group is this one do NOT appear in the list. meteora:x:990: is empty and yet the user meteora belongs to the group, because it is their primary group in /etc/passwd. That is why id nuria is more reliable than reading /etc/group: it combines both sources.
Two rules when editing these files. Use vipw, vipw -s and vigr instead of a bare editor: they lock against simultaneous edits and validate the syntax — a corrupt /etc/passwd leaves the system unable to resolve any user name. And validate with pwck and grpck after any manual change.
System accounts versus human accounts
| System account | Human account | |
|---|---|---|
| Example | meteora (990), www-data (33) |
carlos (1001), nuria (1002) |
| UID range on Debian | 1–999 | 1000 and up |
| Shell | /usr/sbin/nologin |
/bin/bash |
| Password | Locked (! or *) |
Valid hash |
| Directory | Functional (/var/lib/meteora) or nonexistent |
/home/user |
| Interactive login | Never | Yes |
| Lifecycle | That of the service | That of the employment relationship |
Separation by range is not a kernel rule: it is a convention applied by useradd and login reading /etc/login.defs. Its value is operational: it lets you write audits of the form "every account with UID ≥ 1000 must have a second factor" or "no account with UID < 1000 may have a shell". This is how meteora was created:
sudo groupadd --system --gid 990 meteora
sudo useradd --system --uid 990 --gid meteora --no-create-home \
--home-dir /var/lib/meteora --shell /usr/sbin/nologin \
--comment "Meteora Service" meteora
sudo passwd -l meteora # explicitly lock the passwordEvery option is a decision. --system places the UID in the service range and avoids creating a personal group. --no-create-home is correct because /var/lib/meteora is created by the package with the 2750 permissions of 04-06, not by useradd with its own. --shell /usr/sbin/nologin closes off login. And passwd -l is explicit even though useradd --system already leaves the account locked: security is declared, not assumed.
Managing an account and its lifecycle
# ONBOARDING a person
sudo useradd --create-home --shell /bin/bash --comment "Nuria Vidal,,," nuria
sudo passwd nuria && sudo chage -d 0 nuria # force a change on first login
# ROLE CHANGE
sudo usermod -aG adm nuria # the -a is mandatory!
sudo gpasswd -d nuria meteora # remove from a group
# EXPIRY
sudo chage -M 365 -m 1 -W 14 -I 30 nuria # max, min, warning, inactivity
sudo chage -E 2027-06-30 temp_contractor # end date of a contract
sudo chage -l nuria # check the statusThe classic mistake is on the role-change line: usermod -G without the -a replaces the entire list of supplementary groups. A well-intentioned usermod -G adm carlos leaves him out of sudo and out of meteora, and the effect is not noticed until someone needs those accesses. The -a stands for append, and the rule is never to write -G without it.
graph LR
A["ONBOARDING<br/>Minimal account<br/>and groups"] --> B["OPERATION<br/>Periodic access<br/>review"]
B --> C["ROLE CHANGE<br/>Add the new<br/><b>and REMOVE the old</b>"]
C --> B
B --> D["OFFBOARDING<br/>Close ALL the<br/>access paths"]
D --> E["ARCHIVING<br/>Reassign<br/>ownership"]
Incidents always pile up at the same two points.
The role change and the accumulation of privileges. When someone changes job the new accesses are added and the old ones are almost never removed; after five years and three teams they have accumulated access to half the company without anyone having decided it. It is the silent violation of least privilege, and the only defense is the periodic access review: every six months, check that every group and every sudo rule is still justified. It is boring and it works.
Offboarding, which is the biggest real risk in the cycle. An account still alive after a person leaves is a valid credential with no owner and no supervision. And the usual mistake is not forgetting the offboarding, but doing it halfway. It has five blocks and none replaces another: close the four access paths (password, shell, account expiry and authorized_keys), withdraw the privileges (groups, sudo rules, ACLs, cron jobs), cut the live sessions, inventory and reassign the files, and rotate the secrets that person knew. The complete procedure with its commands is Exercise 2.
The most forgotten step is withdrawing authorized_keys, and it is devastating: usermod -L locks the password but does not prevent logging in with a public key, because SSH never consults the hash when authentication is by key. The second most forgotten is cutting sessions: locking an account does not evict whoever is already inside.
On deleting the account with userdel -r: do it only after archiving what is needed. And here comes the obligatory warning: the retention of people's data, access logs and retention periods have legal implications (GDPR and employment law); before setting a deletion policy, consult it with the compliance officer or with legal counsel. It is not a technical decision.
Authentication: the three factors
Authenticating is proving you are who you say you are, and every method fits into three families:
| Factor | Basis | Examples | Weakness |
|---|---|---|---|
| Something you know | Knowledge | Password, PIN, passphrase | Guessed, reused, shared, leaked |
| Something you have | Possession | SSH key, TOTP, FIDO2 key | Lost, stolen, clonable depending on the type |
| Something you are | Biometrics | Fingerprint, face, iris | Cannot be changed if compromised |
Multi-factor authentication (MFA) requires elements from two different families: password plus security question is not MFA — both are "something you know"; password plus TOTP code is. It is the separation of privilege of 05-01 applied to identity: two independent conditions instead of one.
The weakness of biometrics deserves emphasis because it is usually sold as the strongest: a compromised fingerprint is a permanent problem. You can change a password in ten seconds; you cannot change fingers. That is why it is correctly used as a local factor — unlocking a device that holds the real key — and not as a credential that travels over the network. For meteo-01 the goal is clear: SSH with a public key (something you have) protected by a passphrase (something you know), and a second factor for access from outside the management network.
How a password is stored
A rule with no exceptions: a password is never stored, neither in the clear nor reversibly encrypted. What is stored is the result of running it through a key derivation function (KDF), and at authentication time the operation is repeated and the results are compared.
Why SHA-256 will not do: because it is designed to be fast, and that speed plays into the attacker's hands — a GPU computes billions per second. A password KDF is designed with three opposite properties: a unique, random salt, which makes two users with the same password produce different hashes and invalidates precomputed tables; an adjustable work cost, to make it deliberately slow and to raise the cost as hardware improves; and a memory cost, which cancels out the advantage of GPUs and ASICs, with lots of computation and little memory per unit.
| Algorithm | Prefix | Memory cost | Verdict |
|---|---|---|---|
| Traditional DES | (13 characters) | No | Obsolete: only 8 useful characters |
| MD5-crypt | $1$ |
No | Obsolete |
| SHA-256/512-crypt | $5$, $6$ |
No | Acceptable with many rounds |
| bcrypt | $2b$ |
Little | Good, very well tested since 1999 |
| yescrypt | $y$ |
Yes | Default on current Debian |
| Argon2id | $argon2id$ |
Yes | Current recommendation for new development |
And this is how the real /etc/shadow field is read:
$y$j9T$FvB2kXqR8mNpL4wZ$3xKm9QvW7rT2yH5nB8cF4dG6jK1mP0sA9zX3vC7bN2e │ │ │ │ │ │ │ └─ HASH: result of the derivation │ │ └──────────────────────────────── SALT: random, different per user │ └───────────────────────────────────────── PARAMETERS: time and memory cost └──────────────────────────────────────────── ALGORITHM: y = yescrypt
The salt is stored in the clear and rightly so: it is not a secret, its purpose is to guarantee that every password has to be attacked separately, and the system needs it to repeat the computation. At authentication time, the system reads the line, extracts the algorithm, the parameters and the salt, recomputes with exactly those values and compares in constant time, so as not to leak information through the response time.
grep ENCRYPT_METHOD /etc/login.defs # ENCRYPT_METHOD YESCRYPT
grep YESCRYPT_COST /etc/login.defs # YESCRYPT_COST_FACTOR 5The cost factor is the lever that compensates for advances in hardware: raising it multiplies the work of every attempt, both for the system and for the attacker. The practical criterion is to set it so that verifying a password costs between 100 and 500 milliseconds on the production hardware: imperceptible for someone authenticating once, devastating for someone trying millions. The same applies to any application of your own: never implement your own scheme, use libxcrypt, bcrypt or argon2 with reviewed parameters.
Attacks on credentials and their countermeasures
Described at a conceptual level, so you can defend against them. There are no offensive instructions or tools here: every row closes with the countermeasure, which is what interests us.
| Attack | What it consists of (concept) | Countermeasure on meteo-01 |
|---|---|---|
| Dictionary | Trying common words and variations | High minimum length, checking against leaked lists |
| Brute force | Walking the space of combinations | Length: every character multiplies the space |
| Precomputed tables | Looking up hashes computed in advance | The salt: it renders them completely useless |
| Reuse | Trying credentials leaked from other services | Password manager, unique passwords, MFA |
| Spraying | One very common password against many accounts | Detection by origin, not only by account; MFA |
| Interception | Capturing the credential in transit | TLS and SSH always; never cleartext protocols |
| Social engineering | Getting the person to hand it over | Training, verification, phishing-resistant MFA (FIDO2) |
The quantitative conclusion that organizes half that table: length beats complexity. With 95 printable characters, every additional character multiplies the search space by 95, whereas replacing an a with an @ multiplies it by less than two — and attack dictionaries have known that substitution for decades. A passphrase of four loosely related words is more resistant and far more memorable than P@ssw0rd!, which additionally satisfies any complexity policy on paper. That is why modern guidance recommends requiring length, not complexity, and eliminating mandatory periodic expiry, which only produces predictable increments: it is the psychological acceptability of 05-01 turned into policy.
sudo apt install libpam-pwquality
# /etc/security/pwquality.conf
minlen = 14 # length: the main defense
minclass = 3 # three character classes, without demanding all four
dictcheck = 1 # rejects dictionary words
usercheck = 1 # rejects variations of the user name
# /etc/security/faillock.conf
deny = 5 # 5 failures...
unlock_time = 900 # ...lock for 15 minutes
fail_interval = 900 # counted within a 15-minute window
even_deny_root = 0 # CAUTION: locking root can lock you outThree comments that avoid real problems. deny=5 with unlock_time=900 makes brute force unfeasible: 480 attempts a day against the millions per second the attack needs. even_deny_root=0 is deliberate: if an attacker can lock root by trying passwords, they have achieved a denial of service against administration precisely when it is most needed. And temporary unlocking is preferable to permanent locking, because a permanent lock turns any spraying campaign into the paralysis of the entire organization. You query it with faillock --user carlos and clear it with --reset.
PAM: the architecture that decides whether you get in
Before PAM, every program that authenticated — login, su, sshd, passwd — carried its own code, and adding a new method meant recompiling them all. PAM (Pluggable Authentication Modules) inserts a level of indirection: programs call the library, and the library consults a file that decides which modules to run and in what order.
graph LR
A["sshd / login / sudo"] --> B["libpam"] --> C["/etc/pam.d/<service>"]
C --> D["auth<br/>are they who they claim?"]
C --> E["account<br/>may they get in NOW?"]
C --> F["password<br/>change the credential"]
C --> G["session<br/>set up and tear down"]
| Stack | Question | Typical modules |
|---|---|---|
| auth | Are they who they claim to be? | pam_unix, pam_faillock, pam_u2f, TOTP |
| account | Being who they are, may they get in now? | pam_nologin, pam_time, pam_access |
| password | How is the credential changed? | pam_pwquality, pam_unix |
| session | What to set up before and clean up after? | pam_limits, pam_systemd, pam_mkhomedir |
The distinction between auth and account is the hardest one and the most useful: an expired account authenticates correctly — the password is valid — and still does not get in, because the account stack denies it. They are two independent decisions.
| Flag | If the module fails | If it succeeds |
|---|---|---|
required |
The stack will fail, but it keeps running to the end | Continues |
requisite |
Stops immediately and returns the failure | Continues |
sufficient |
It is ignored and processing continues | Immediate success if no earlier required module failed |
optional |
It is ignored, unless it is the only module | Continues |
required versus requisite has an elegant justification: required keeps running the stack even when it already knows it is going to fail, so that an attacker cannot deduce at which point it failed by measuring the response time.
# /etc/pam.d/sshd — AUTHENTICATION auth required pam_faillock.so preauth silent # locked out by failures? auth [success=1 default=ignore] pam_unix.so # UNIX password auth required pam_faillock.so authfail # records the failure auth required pam_deny.so # final denial auth required pam_permit.so # ACCOUNT account required pam_nologin.so # does /etc/nologin exist? nobody gets in account required pam_unix.so # expired? locked? account required pam_faillock.so # over the failure threshold? # PASSWORD password requisite pam_pwquality.so retry=3 password required pam_unix.so obscure yescrypt shadow # SESSION session required pam_limits.so # applies /etc/security/limits.conf session required pam_unix.so # records login and logout in wtmp session optional pam_systemd.so # creates the systemd session
The auth stack has a trick to it. [success=1 default=ignore] means "if pam_unix succeeds, skip 1 line", which skips authfail and reaches pam_permit.so, which grants access. If the password fails, there is no skip: it runs authfail — which increments the counter — and then pam_deny.so. It is a conditional jump, and once you see it that way any PAM file reads without difficulty. In account, pam_nologin implements a very useful mechanism: if /etc/nologin exists, nobody except root logs in and its contents are displayed; it is the standard way to empty a server before an intervention.
An example policy change, restricting SSH to certain groups and to a time window:
# In /etc/pam.d/sshd, inside the account stack: account required pam_access.so account required pam_time.so # /etc/security/access.conf — whitelist, and a final denial + : root carlos (admins) : ALL - : ALL : ALL # /etc/security/time.conf — interns, working hours only sshd ; * ; interns ; Wk0800-1900
access.conf is read from top to bottom and the first line that matches decides: root, carlos and the admins group are explicitly allowed, and the last line denies everything else. It is exactly the fail-safe defaults principle of 05-01.
Practical warning. A mistake in
/etc/pam.d/can lock you out of the system irrecoverably, root included. Keep one root session open, open another to verify the change, and have the physical console or the KVM at hand. If the new one fails, the one still open lets you undo.
Public key authentication with SSH
Asymmetric cryptography, in two paragraphs. Two related keys are generated: a private one, which never leaves your machine, and a public one, which you can hand out without risk. What is done with one can only be undone with the other, and — this is the essential part — knowing the public one does not let you deduce the private one.
To authenticate, the server sends a random piece of data and asks for it to be signed with the private key; whoever holds it produces a signature that the server verifies with the public key it already stores. The private key never travels over the network, not even encrypted. Compare it with a password, which does travel — protected by the channel, but it travels — and which the server has to process: if the server is compromised, your password leaks; your private key does not.
# 1. Generate the pair (on YOUR machine, never on the server)
ssh-keygen -t ed25519 -a 100 -C "carlos@laptop-2026" -f ~/.ssh/id_meteo01
# -a 100 : rounds protecting the key with the passphrase. ALWAYS set one.
# -rw------- id_meteo01 ← PRIVATE: mode 600
# -rw-r--r-- id_meteo01.pub ← public: handed out
# 2. Install it on the server and check permissions (sshd REQUIRES them)
ssh-copy-id -i ~/.ssh/id_meteo01.pub carlos@meteo-01
# drwx------ ~/.ssh -rw------- ~/.ssh/authorized_keys
# 3. The agent: type the passphrase ONCE per session
eval "$(ssh-agent -s)" && ssh-add -t 8h ~/.ssh/id_meteo01The points to understand. The key is generated on your machine: if you generate it on the server, the private key has existed on a system you do not fully control and has passed through its disks and its backups. The passphrase effectively turns it into a second factor: whoever steals the file also needs to know it. The permissions are mandatory, not a recommendation: sshd rejects an authorized_keys, a ~/.ssh or even a $HOME that is too open, because anyone able to write there would add their own key. And the agent exists for psychological acceptability: without it, typing the passphrase fifty times a day leads straight to removing it; -t 8h limits the damage if someone leaves a session open.
# /etc/ssh/sshd_config.d/99-meteora.conf PermitRootLogin no # never root directly: log in and then sudo PasswordAuthentication no # public keys ONLY KbdInteractiveAuthentication no # closes the other password route AuthenticationMethods publickey AllowGroups admins # whitelist of who may get in MaxAuthTries 3 LoginGraceTime 30 AllowAgentForwarding no # stops your agent being reused from the server ClientAliveInterval 300
Every line corresponds to a principle from 05-01. PermitRootLogin no is separation of privilege and traceability: it forces you to log in with your named account and elevate with sudo, so that every action is attributed to a person rather than to a shared root. PasswordAuthentication no eliminates dictionary, brute force, spraying and reuse in one stroke: if there is no password to try, there is no password attack. AllowGroups is a whitelist. And AllowAgentForwarding no closes a little-known risk: with forwarding enabled, someone with root on the server can use your agent to hop to other machines where your key is valid.
Before closing the session: validate with
sudo sshd -t, reload withsystemctl reload sshand open a second session to check without closing the first. Disabling passwords without having tested the key is the most common way of losing access to a remote server.
Controlled privilege elevation: su and sudo
An administrator needs privileges now and then, not all day. Always working as root violates least privilege in the worst way: one mistyped rm -rf and there is no safety net. There are two ways to go up, and they are very different.
su |
sudo |
|
|---|---|---|
| Password it asks for | The target user's (root's) | Your own |
| Granularity | All or nothing: you become the user | Per command, user and machine |
| Duration | A whole shell | One command (with a cache of minutes) |
| Traceability | "Someone ran su"; the rest is anonymous |
Every command is logged with who ran it |
| Shared secret | The whole team knows root's password | Nobody needs to know it |
| Revoking one person | Change root's password and tell everyone | Remove one line from sudoers |
| Verdict | Legacy; useful for su - meteora when debugging |
The correct way |
The decisive row is the shared secret: with su, five people know root's password and no action is attributable; with sudo, root's password may not even exist — on Debian it is locked by default — and withdrawing access is deleting one line.
visudo is not optional. It checks the syntax before saving and refuses to write an invalid file; an /etc/sudoers with an error leaves the system without sudo, and if PermitRootLogin no is also active and root has no password, you have lost every administration route except the physical console. The syntax of a rule is who where = (as whom) which commands:
## --- Level 0: full access (Debian's sudo group) ---
%sudo ALL=(ALL:ALL) ALL
## --- Level 1: only what the team needs ---
Cmnd_Alias METEORA_SVC = /usr/bin/systemctl start meteo-api, \
/usr/bin/systemctl stop meteo-api, \
/usr/bin/systemctl restart meteo-api
%meteora-ops ALL=(root) METEORA_SVC
## --- Level 2: passwordless ONLY for the harmless and automated ---
%monitoring ALL=(root) NOPASSWD: /usr/bin/systemctl status meteo-api
## --- General hygiene ---
Defaults env_reset, timestamp_timeout=5, passwd_tries=3
Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
Defaults logfile="/var/log/sudo.log", log_input, log_outputThe four important elements. The Cmnd_Alias entries group commands together and make the policy readable, which is economy of mechanism applied to configuration. env_reset and secure_path are critical: without them, a user could manipulate PATH or variables such as LD_PRELOAD so that the privileged command loads code of their own. timestamp_timeout=5 is the number of minutes the password is cached: 0 asks every time — secure and exhausting, and you already know where that leads — and high values leave a window if someone walks away from the terminal. And log_input, log_output record the full session of elevated commands, which is invaluable in an investigation.
On NOPASSWD: its risk is not theoretical. It turns any compromise of the account — an open session, a stolen key — into immediate privilege with no barrier at all, removing the implicit second factor of typing the password. Use it only for commands that are read-only and take no free-form arguments, run by automation that cannot type, and never with ALL.
And the property that makes sudo a security tool and not merely a convenience: every elevation is logged, on success and on failure.
Sep 01 11:42:17 meteo-01 sudo[4821]: carlos : TTY=pts/0 ; PWD=/home/carlos ;
USER=root ; COMMAND=/usr/bin/systemctl restart meteo-api
Sep 01 11:45:03 meteo-01 sudo[4855]: nuria : 3 incorrect password attempts ;
TTY=pts/2 ; USER=root ; COMMAND=/usr/bin/cat /etc/shadowEach line contains who, from where, as whom and what. The second one is exactly the event that should trigger an alert. Check your own permissions with sudo -l, someone else's with sudo -l -U nuria, and forget the cached password with sudo -k. The systematic handling of these logs is Auditing, Logging and Incident Response.
Privilege escalation: defensive catalog and auditing
Privilege escalation is moving into a protection domain with more authority — in the vocabulary of 05-01, an unplanned domain change. We treat it exclusively from the defensive side: which configurations make it possible and how to detect them. No exploitation techniques are described.
| Failure category | Why it enables escalation | How it is detected |
|---|---|---|
| Unnecessary setuid binaries | They run as their owner: a bug in them is a bug with privilege | find / -perm -4000 against a reference |
| Files with capabilities | The same, and they are invisible in ls -l |
getcap -r /, with a reference |
Broad sudo rules |
Many commands can run others or write arbitrary files | sudo -l, review of sudoers.d/ |
| Wildcards in paths | A * matches unforeseen paths, .. included |
Manual review; avoid * |
| Writable privileged scripts | If you can edit what root runs, you run as root | find for group- or other-writable files |
| Badly permissioned scheduled jobs | A root cron job running an editable script |
Permissions of /etc/cron* |
| Relative paths in privileged scripts | The program executed depends on PATH |
Code review; absolute paths |
| Secrets in the environment or the history | A key visible in ps, in environ or in .bash_history |
grep through history files |
| Services running as root needlessly | A bug in the service is a bug with UID 0 | ps -eo user,comm --sort user |
| Unpatched software | Known vulnerabilities in the kernel or the libraries | apt list --upgradable |
#!/bin/bash
# privilege-audit.sh — run as root, periodically
echo "[1] Accounts with UID 0 (only root should appear)"
awk -F: '($3==0){print " "$1}' /etc/passwd
echo "[2] Accounts with NO password"
awk -F: '($2==""){print " CRITICAL: "$1}' /etc/shadow
echo "[3] System accounts WITH an interactive shell"
awk -F: '($3<1000 && $3>0 && $7!~/nologin|false/){print " "$1" -> "$7}' /etc/passwd
echo "[4] setuid/setgid and [5] capabilities: by DIFFERENCE against the reference"
find / -xdev \( -perm -4000 -o -perm -2000 \) -type f 2>/dev/null | sort > /tmp/suid.now
getcap -r / 2>/dev/null | sort > /tmp/caps.now
diff /root/ref/suid.ref /tmp/suid.now | grep '^>' || echo " setuid: no changes"
diff /root/ref/caps.ref /tmp/caps.now | grep '^>' || echo " caps: no changes"
echo "[6] sudo rules with NOPASSWD or wildcards"
grep -rnE 'NOPASSWD|\*' /etc/sudoers /etc/sudoers.d/ 2>/dev/null | grep -v '^\s*#'
echo "[7] Root scheduled jobs writable by others"
find /etc/cron* /var/spool/cron -type f \( -perm -0002 -o -perm -0020 \) -ls 2>/dev/null
echo "[8] Services running as root"
ps -eo user:16,comm --no-headers | awk '$1=="root"' | sort -u -k2 | head -30
echo "[9] Possible secrets in shell history files"
grep -rlE '(password|token|api[_-]?key|secret)=' /home/*/.bash_history 2>/dev/nullHow to read it. Blocks [1] to [3] must produce fixed, known results; any novelty demands an explanation, and a system account with a shell is exactly what an attacker creates to persist. [4] and [5] are detection by difference: what matters is not the complete list but what has appeared since last time; [5] is the most forgotten and the quietest, because ls -l does not show capabilities. [6] does not flag errors but things to review: every NOPASSWD and every wildcard needs a written justification. [7] looks for the most profitable pattern for an attacker: a file they can write that root runs on its own. [8] should be as short as possible. And [9] is a reminder that a typed secret stays in the history in the clear and forever, besides having been visible in ps while the command ran.
How meteo-01 is designed to contain a compromise
| Decision | What it prevents if meteo-api falls |
|---|---|
meteora with nologin and a locked password |
It is useless for logging in, over SSH or locally |
| Configuration and binary owned by root | It cannot rewrite itself or persist that way |
| No setuid binary of the service's own | There is no route up to root in the package |
/var/lib/meteora with nosuid,nodev,noexec |
It cannot run a binary it drops there |
NoNewPrivileges=yes in the unit |
It gains no privilege even by running a system setuid binary |
Reduced CapabilityBoundingSet= |
It cannot mount, load modules or debug processes |
meteora is not in sudo and has no rules |
There is no rule for it to invoke |
| AppArmor profile with no execution rules (05-01) | It does not get a shell, the first step of almost everything |
Logs with group adm and chattr +a |
It cannot easily erase its traces |
No single line is enough on its own. Together they turn a compromise of the application into an incident confined to the application, which is the goal: not to avoid every bug — impossible — but to keep a bug from spreading.
A reminder about responsibility. Everything above is for auditing and defending your own systems. Any security check on someone else's system requires express written authorization from the person responsible; without it, it may constitute a criminal offense regardless of intent. And decisions about accounts, access logs and the retention of personal data have GDPR and employment law implications: review them with compliance or legal counsel.
Common Mistakes and Tips
Believing that passwd -l closes an account. It locks the password and does not touch SSH key authentication. A complete offboarding requires four actions — password, nologin, expiry and authorized_keys — plus cutting the live sessions.
Using usermod -G without -a. It replaces the entire list of supplementary groups instead of adding to it, and leaves the person out of sudo and out of whatever else they had. The effect shows up days later.
Expecting a group change to take effect immediately. Groups are fixed at login. id shows the change because it re-reads the file, but the process keeps its credentials: compare against /proc/<pid>/status and restart the session or the service.
Editing /etc/sudoers without visudo. A syntax error leaves the system without sudo, and with root having no password and no direct SSH access you have lost administration. Use visudo and separate files in /etc/sudoers.d/.
NOPASSWD: ALL "temporarily". It removes the only barrier against a hijacked session. If sudo is that annoying, raise timestamp_timeout; do not remove the password.
Generating the SSH key on the server. The private key must be born and die on your machine; if you generate it there, it has existed on a system you may not control and has passed through its backups.
Disabling PasswordAuthentication without having tested the key. It is the most common way to lock yourself out of a remote server. Validate, reload and open a second session before closing the first. The same goes for any change in /etc/pam.d/.
Confusing the PAM stacks. auth answers "are they who they claim to be?" and account answers "may they get in now?". An expired account authenticates fine and still does not get in.
Auditing setuid and forgetting getcap. A binary with cap_dac_override shows no mark at all in ls -l. Keep two reference lists and compare them periodically.
Tip: use sudo -l as a reflex. Before assuming what you can do, ask; and as an administrator, sudo -l -U user is the quickest way to audit a person's real privileges, including the ones reaching them through groups nobody remembers any more.
Exercises
Exercise 1: interpreting a system's identity
On your machine or a test virtual machine: (a) interpret your /etc/passwd line and that of a service account field by field; (b) identify the algorithm, the parameters and the salt of a real hash from /etc/shadow, and explain what !, * and the empty field mean; (c) locate every account with UID 0, the system ones with an interactive shell and those with no password, explaining why each check matters; (d) demonstrate empirically that a usermod -aG does not affect an open session, by comparing id with /proc/$$/status.
Exercise 2: securely offboarding an account
The analyst nuria is leaving the company. She had an account with a shell, an installed SSH key, membership of adm, an ACL entry on /var/lib/meteora/readings (04-06), a rule in /etc/sudoers.d/nuria, a daily cron job, files in /home/nuria and in /var/lib/meteora/export, and she knew the API key of the data provider. Write the complete offboarding procedure, in order, with the exact command for each step, the justification of what would be left open if it were omitted, the final verification, and which decisions must be consulted with legal counsel.
Exercise 3: designing the team's sudo policy
Design /etc/sudoers.d/meteora for three profiles: %meteora-ops, which starts, stops, restarts and queries meteo-api, ingestor and aggregator and reads their logs; %meteora-dba, which runs the backup and the restore; and %monitoring, an automated account that only queries status, with no password. Justify the Defaults. Then explain why these three alternative rules would be dangerous and what exactly they would allow:
%meteora-ops ALL=(root) NOPASSWD: /usr/bin/systemctl * %meteora-dba ALL=(root) /usr/bin/tar * %meteora-ops ALL=(root) /usr/bin/vim /etc/meteora/meteora.conf
Solutions
Solution 1
getent passwd $USER ; getent passwd www-data # (a)
sudo awk -F: '{print $1, substr($2,1,3)}' /etc/shadow # (b)
sudo awk -F: '($3==0){print "UID 0: "$1}' /etc/passwd # (c)
sudo awk -F: '($3<1000&&$3>0&&$7!~/nologin|false/){print "shell: "$1}' /etc/passwd
sudo awk -F: '($2==""){print "NO PASSWORD: "$1}' /etc/shadow(a) carlos:x:1001:1001:Carlos Ruiz,Systems,,:/home/carlos:/bin/bash is the login name; x, which points to /etc/shadow; UID 1001, the real identity as far as the kernel is concerned; the primary GID; a descriptive GECOS; $HOME; and an interactive shell. The service one, www-data:x:33:33:...:/usr/sbin/nologin, differs in three decisive points: a UID below 1000, a functional directory and nologin, which prevents logging in even if someone knew a credential.
(b) $y$j9T$FvB2kXqR8mNpL4wZ$3xKm... breaks down at each $: y is yescrypt; j9T are the time and memory cost parameters; FvB2kXqR8mNpL4wZ is the salt, random per user and stored in the clear because it is not a secret and the system needs it to repeat the computation; the rest is the hash. ! is a reversible lock with the hash preserved behind it; * means "never had one"; and empty means access with no credential.
(c) All three matter for different reasons. A second account with UID 0 is root under another name — identity is the number — and it is a persistence technique that appears on no list of "administrators". A system account with a shell is a login route that should not exist and a common backdoor pattern. And an account with no password gets in with no credential. On a healthy system the first returns only root, the third returns nothing, and the second returns only justified cases such as sync.
(d)
sudo groupadd testgrp && sudo usermod -aG testgrp $USER
id -Gn # ← testgrp DOES appear (id re-reads /etc/group)
grep Groups /proc/$$/status # ← its GID does NOT (the process did not change)
sg testgrp -c 'grep Groups /proc/$$/status' # with new credentials, it DOES
sudo groupdel testgrpThe discrepancy is the demonstration: id queries the file, and the kernel uses the credentials the process has carried since it was created. Supplementary groups are fixed at login and inherited across fork (02-01), so only a new session picks them up. It is the explanation for "I already gave them the group and it still does not work".
Solution 2
DATE=$(date +%F); mkdir -p /root/offboarding/nuria-$DATE
# --- 0. PRIOR INVENTORY (before touching anything) ---
sudo -l -U nuria > /root/offboarding/nuria-$DATE/sudo.txt
sudo crontab -l -u nuria > /root/offboarding/nuria-$DATE/cron.txt 2>/dev/null
sudo find / -xdev -user nuria -ls 2>/dev/null > /root/offboarding/nuria-$DATE/files.txt
# --- 1. CLOSE THE FOUR ACCESS PATHS ---
sudo usermod -L nuria # a) password
sudo usermod -s /usr/sbin/nologin nuria # b) shell
sudo chage -E 0 nuria # c) account expired
sudo mv /home/nuria/.ssh/authorized_keys \
/root/offboarding/nuria-$DATE/authorized_keys.revoked # d) SSH KEYS
# --- 2. WITHDRAW PRIVILEGES ---
sudo gpasswd -d nuria adm && sudo rm -f /etc/sudoers.d/nuria && sudo visudo -c
sudo setfacl -x u:nuria /var/lib/meteora/readings
sudo setfacl -x d:u:nuria /var/lib/meteora/readings # the default ACL too
sudo crontab -r -u nuria
# --- 3. CUT LIVE SESSIONS ---
sudo loginctl terminate-user nuria ; pgrep -u nuria && sudo pkill -KILL -u nuria
# --- 4. FILES: avoid orphans ---
sudo tar --acls -czf /root/offboarding/nuria-$DATE/home.tar.gz /home/nuria
sudo chown -R carlos:meteora /var/lib/meteora/export/nuria-*
# --- 5. ROTATE SECRETS: the provider's API key and shared passwords ---
sudo systemctl restart meteo-apiJustification. Step 0 comes first because if you lock the account before taking inventory you lose the snapshot of what she had, and that snapshot is the starting point of any later investigation.
In step 1, the four substeps close independent paths: (a) password authentication; (b) obtaining a shell even if some route survives; (c) access through PAM's account stack, regardless of how she authenticates. And (d) is the indispensable one and the most forgotten: without it, nuria still gets in over SSH exactly as before, because public key authentication does not consult the password hash.
Step 2 exists because privileges are tied to the name and the UID, not to the ability to log in: if UID 1002 is reassigned, the new person would inherit the ACL, the group and the sudo rule. The default ACL is a different entry from the ordinary one and both must be removed, and a cron job would keep running even with the account locked. Step 3 is necessary because locking does not evict whoever is already inside. Step 4 avoids the orphans of 04-06, which would be inherited by the next UID 1002; --acls preserves the ACLs that a plain tar would silently lose. And step 5 starts from the premise that a secret known to someone who has left must be considered compromised: it cannot be "unknown" again.
# --- VERIFICATION ---
sudo chage -l nuria ; sudo -l -U nuria ; ls -l /home/nuria/.ssh/
getfacl /var/lib/meteora/readings | grep nuria || echo "ACL clean"
who | grep nuria || echo "no sessions"
grep -r nuria /etc/sudoers /etc/sudoers.d/ /etc/group || echo "no references"Legal counsel. The definitive deletion (userdel -r), the retention period of the /home/nuria archive, the handling of her email and the retention of the access logs that identify her have GDPR and employment law implications, where retention obligations can clash with data minimization. They must be agreed with compliance or legal counsel before being carried out, and the procedure must set them down in writing so that nothing is improvised case by case.
Solution 3
# /etc/sudoers.d/meteora — ALWAYS edit with: visudo -f /etc/sudoers.d/meteora
# Every verb and every unit enumerated: NO wildcards
Cmnd_Alias METEO_CTL = /usr/bin/systemctl start meteo-api, /usr/bin/systemctl stop meteo-api, \
/usr/bin/systemctl restart meteo-api, /usr/bin/systemctl status meteo-api, \
/usr/bin/systemctl start ingestor, /usr/bin/systemctl stop ingestor, \
/usr/bin/systemctl restart ingestor, /usr/bin/systemctl status ingestor, \
/usr/bin/systemctl start aggregator, /usr/bin/systemctl stop aggregator, \
/usr/bin/systemctl restart aggregator, /usr/bin/systemctl status aggregator
Cmnd_Alias METEO_LOG = /usr/bin/journalctl -u meteo-api*, /usr/bin/journalctl -u ingestor*, \
/usr/bin/journalctl -u aggregator*
Cmnd_Alias METEO_BAK = /usr/local/sbin/meteora-backup.sh, /usr/local/sbin/meteora-restore.sh
%meteora-ops ALL=(root) METEO_CTL, METEO_LOG
%meteora-dba ALL=(root) METEO_BAK
%monitoring ALL=(root) NOPASSWD: /usr/bin/systemctl status meteo-api
Defaults:%meteora-ops timestamp_timeout=5, log_input, log_output
Defaults:%meteora-dba timestamp_timeout=0
Defaults env_reset, passwd_tries=3, logfile="/var/log/sudo.log"
Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"The Defaults. env_reset and secure_path are the critical ones: they prevent manipulating PATH, LD_PRELOAD or other variables so that the privileged command loads the user's code, something that would turn any rule, however narrow, into arbitrary execution as root. timestamp_timeout=5 gives five minutes of caching for frequent operations, a balance between security and psychological acceptability; for %meteora-dba it is set to 0 because restoring a backup is destructive and infrequent, and must always be confirmed. log_input, log_output records the sessions of the group that touches the service most. And the NOPASSWD for %monitoring is acceptable only because the command is single, read-only, takes no free-form arguments and is run by automation that cannot type.
Why the three alternatives are dangerous.
1. NOPASSWD: /usr/bin/systemctl *. The wildcard covers every subcommand and every unit on the system, not just Meteora's: it includes stopping the firewall, stopping auditd or disabling SSH, and above all the subcommands that create or edit units — anyone who can write a unit makes the system run whatever they want as root at the next boot. With NOPASSWD, it amounts to granting root with no barrier at all. Fix: enumerate the exact commands with the unit included, as in METEO_CTL.
2. /usr/bin/tar *. tar cannot be constrained with a wildcard, for two independent reasons. When extracting as root, it can write to any absolute path contained in the archive — /etc/sudoers.d/, /root/.ssh/authorized_keys, /etc/shadow — and the contents of the archive are controlled by whoever supplies it. And it accepts options that run external programs as part of its normal operation, which a * leaves allowed. Fix: a script of your own with a fixed path and validated parameters, as in METEO_BAK.
3. /usr/bin/vim /etc/meteora/meteora.conf. It looks like the most constrained one and it is just as dangerous: full editors run system commands and open other files from inside, so once vim runs as root the argument restriction is irrelevant. The same goes for less, more, man, awk or find. Fix: sudoedit (or sudo -e), which copies the file to a temporary one, edits it with the user's permissions and puts it back — the editor never runs as root: %meteora-ops ALL=(root) sudoedit /etc/meteora/meteora.conf.
The general lesson: a sudo rule does not constrain what the user can do, but what they can execute. Faced with any rule, ask yourself: can this command, with these arguments, write an arbitrary file or run another program?
Conclusion
Identity in Linux is a number, the UID; everything else lives in /etc files the kernel does not even read, and hence the consequence that organizes the lesson: whoever controls the number controls the identity, which is why a second account with UID 0 is root under another name. Every process carries three UIDs — real, effective and saved — because authority and identity must be able to differ and both remain available: that is what lets passwd write to /etc/shadow with the effective one while using the real one to know whose password to change. And dropping privilege — setgroups, setgid, setuid, in that order, and verifying that there is no way back — is least privilege over time turned into code.
The three files are read field by field. /etc/passwd has seven, with the x that reminds us why the hashes were taken out of a world-readable file, and a seventh field, nologin, that is a whole layer of defense for meteora. /etc/shadow has nine, and the second says it all: $y$... is a valid password, ! a reversible lock, * "never had one" and empty is an emergency. /etc/group stores only supplementary members, which is why id is more reliable than reading it. In the lifecycle, the two risk points are universal: the accumulation of privileges during role changes, which only periodic reviews correct, and offboarding, which must close four paths — password, shell, expiry and authorized_keys — cut sessions, reassign files and rotate secrets, because locking the password does not close SSH key access.
In authentication, the three factors and the rule that MFA requires two different families. Passwords are not stored: they are derived with a KDF that provides a unique salt, a work cost and a memory cost, and that is why Debian's $y$j9T$salt$hash reads as algorithm, parameters, salt and result, with the salt in the clear because it is not a secret. Against dictionary, brute force, precomputed tables, reuse and spraying, the countermeasures are concrete: length before complexity, pam_pwquality, pam_faillock with temporary unlocking and, above all, MFA. PAM organizes all of that into four stacks — auth, account, password, session — with flags that read easily once you understand that [success=1] is a conditional jump, and with the distinction between authenticating correctly and being allowed in right now split across separate stacks.
For meteo-01 the correct configuration is a public key with a passphrase and an agent, with PermitRootLogin no and PasswordAuthentication no: if there is no password to try, a whole family of attacks disappears, and logging in with a named account makes every action attributable. Elevation is done with sudo, not su: per-command granularity, no sharing of root's password, revocable by deleting one line and with every elevation logged. Its traps are NOPASSWD, wildcards and the subtlest one: a sudo rule does not constrain what the user can do, but what they can execute, so any command able to launch other programs or write arbitrary paths grants root even when the rule looks narrow. And the defensive catalog of privilege escalation — unnecessary setuid binaries, capabilities invisible in ls -l, broad rules, wildcards, writable scripts, badly permissioned jobs, secrets in the environment, services running as root — is audited with detection by difference against a baseline, because what matters is not the complete list but what has appeared since the last review.
We now have identity and access control. But everything above assumes the attacker comes in through the door: that they try to authenticate, that they abuse a rule, that they use a credential. What if they do not? What happens when the flaw is in the code of meteo-api and no credential at all is needed to trigger it? Which classes of programming error allow it, which defenses does the operating system put underneath — ASLR, non-executable stack, canaries — and how do you check that they are active? And which concrete measures, in order and with their justification, turn a freshly installed Debian into a hardened server?
That is Common Threats and System Hardening, where we will classify the threats to a real server with the trace each one would leave on meteo-01, build Meteora's threat model, look at the classes of program vulnerability and the kernel's defenses, and put together the complete hardening: firewall, isolation with systemd, encryption, secrets management and backups as a security control.
Operating Systems Fundamentals
Module 1: Introduction to Operating Systems
- Basic Concepts of Operating Systems
- History and Evolution of Operating Systems
- Types of Operating Systems
- Main Functions of an Operating System
- Kernel Architecture: Monolithic, Microkernel and Hybrid
- User Mode, Kernel Mode and System Calls
Module 2: Resource Management
- Process Management
- CPU Scheduling
- Memory Management
- Virtual Memory and Paging
- Storage Management
- Device Management
- Drivers, Interrupts and I/O Operations
Module 3: Concurrency
- Concurrency Concepts
- Threads and Processes
- Inter-Process Communication (IPC)
- Synchronization and Mutual Exclusion
- Classic Concurrency Problems
- Deadlocks: Prevention, Detection and Recovery
Module 4: File Structures
- File Systems
- Directory Structures
- Partitions, Mounting and the Virtual File System
- File Management
- Space Allocation, Journaling and Integrity
- File Security and Permissions
Module 5: System Protection and Security
- Protection Principles and Access Control
- Users, Authentication and Privilege Escalation
- Common Threats and System Hardening
- Auditing, Logging and Incident Response
Module 6: Virtualization and Containers
- Virtualization: Hypervisors and Virtual Machines
- Containers: Namespaces and cgroups
- The Operating System in the Cloud
- Mobile and Real-Time Operating Systems
