DEV Community

Cover image for Wild Ride from Raw Syscalls to Figuring Out NSS and libc
Bhushitha Hashan
Bhushitha Hashan

Posted on

Wild Ride from Raw Syscalls to Figuring Out NSS and libc

Man, I’ve always had this nerdy obsession with Linux. From day one, I was diving headfirst into the kernel, writing tiny tools using raw syscalls,basically skipping libc entirely and talking to the kernel like a badass. I made my own headers, wrote my own print routines, opened directories using getdents64, parsed /proc for processes… basically living dangerously in the kernel’s backyard.

At the time, I thought I was the shit doing everything “the real way.” And yeah, it worked. But what I didn’t realize was that I was skipping a whole layer of sanity that exists in libc. I had no idea about all the stuff it actually does. All the buffering, human-readable formatting, and things that make life not completely painful.


That WTF Moment: /etc/nsswitch.conf

Everything hit me when I stumbled upon /etc/nsswitch.conf while poking around network and user stuff. I opened it and literally went:

“What the actual hell is this? Who the fuck uses this? Isn’t the kernel supposed to know this?”

It was super confusing. I was in full raw syscall mode, so the idea that some config file could tell libc how to handle getpwnam() or getaddrinfo() blew my mind. I had no clue why this “signboard” was even necessary.


Okay, Now NSS Makes Sense

Turns out, NSS = Name Service Switch. And yeah, it’s basically a signboard inside libc telling it where the hell to look for information.

When a program calls something like getpwuid() or getpwnam(), it’s asking:

“Who is this user? What’s their username?”

Here’s the thing: the kernel doesn’t know usernames, it only knows numeric IDs, like UID = 1000. That’s where libc + NSS swoop in like superheroes.


How It Actually Works

Say you call getpwnam("Deadpool"). libc does this:

1.Read /etc/nsswitch.conf

  • Finds the line for users:

     passwd: files systemd
    
  • This basically says:

    • First, check local files (/etc/passwd)
    • Then, ask systemd’s dynamic user database

2.Call each NSS module in order

  • First: libnss_files.so.2 → looks in /etc/passwd

    • Found? Cool → return result
    • Not found? Keep going
  • Second: libnss_systemd.so.2 → ask systemd

    • Found? Sweet → return result
    • Not? Continue to next source if any

3.Return a struct passwd to your program

  • Full info: username, UID, GID, home dir, shell, all the juicy stuff

Important: NSS itself is not a running program, just a config file + shared libraries. libc calls them dynamically, no extra processes involved.


The Secret Sauce: libc Beyond Raw Syscalls

Here’s something I didn’t realize at first: libc is more than just a syscall wrapper. Sure, it wraps syscalls like write, read, open, getpid, etc., but it also adds a ton of extra sauce:

  • Buffering and formatting: printf, fopen buffers, getline, etc.
  • Memory management: malloc, free
  • Threading support: pthread_* functions
  • Policy and lookup logic: NSS modules, locale, hostname resolution

Without NSS, this extra sauce works partially:

  • Syscalls themselves still happen correctly (you can open files, read directories, write to stdout)
  • Buffering, formatting, memory, threading still works
  • But any function that needs human-readable names (users, groups, hosts, services) would fail or return NULL, because libc has no map to tell it where to look

NSS Modules Still Use Syscalls Under the Hood

Here’s the other thing that clicked the perfect image on my mind: even NSS modules themselves ultimately use syscalls.

  • For example, libnss_files.so.2 reads /etc/passwd using open, read, close (syscalls).
  • libnss_systemd.so.2 talks to the systemd service using IPC syscalls.
  • So everything still hits the kernel eventually the NSS just adds a layer of logic so libc knows where and how to get the data.

Basically, libc + NSS = fancy middleware. Raw syscalls = truth. NSS just gives meaning to that truth and ensures humans don’t see useless numbers.


How This Hit Me

Before this epiphany, my tools were like:

  • Open directories with getdents64
  • Read /proc/<pid>/status → get numeric UIDs
  • Display numbers only

Which is perfectly fine if you just want raw truth. But it’s boring for humans. Humans want:

PID: 742   User: Deadpool   Process: bash
Enter fullscreen mode Exit fullscreen mode

Not:

PID: 742   UID: 1000   Process: bash
Enter fullscreen mode Exit fullscreen mode

NSS basically translates truth into meaning, and it’s libc that does all the heavy lifting.


Layers of the System (Mental Picture)

Here’s how it finally clicked for me:

Some key nuggets I learned:

  • libc does way more than syscalls its buffering, formatting, threading, memory, and yes, NSS
  • NSS is optional if you only need numbers, but humans will hate your output
  • Systemd has a dynamic user database for service and container users, which NSS can query
  • Raw syscalls = truth, NSS = meaning

My Reflection

Real talk: this was a big “oh shit” moment. I realized that all my badass low level tools were cool, but they weren’t really “user-friendly” or system aware. I had been skipping an entire ecosystem of userspace logic.

Now, I see the value in combining both:

  • Raw syscalls → to understand the kernel and ABIs like a boss
  • libc + NSS → to make tools that are readable, robust, and actually useful

The One-Liner to Finalize It

“Syscalls give you the raw truth; NSS gives libc the damn map to turn that truth into something humans can actually understand.”


This journey taught me not just how Linux works, but also how to think in layers: kernel, ABI, libc, NSS, and human-readable output.

And now, whenever I build a tool, I don’t just make it work, I know why it works, what layer is doing what, and how the pieces actually fit together.


Top comments (0)