Skip to content

feat: resolve real Docker container names in CONTAINER column - #2125

Open
headersalreadysent wants to merge 1 commit into
htop-dev:mainfrom
headersalreadysent:docker-container-names
Open

headersalreadysent wants to merge 1 commit into
htop-dev:mainfrom
headersalreadysent:docker-container-names

Conversation

@headersalreadysent

Copy link
Copy Markdown

htop's CONTAINER column is derived purely from the cgroup scope name (linux/CGroupUtils.c). For processes inside Docker containers it has always shown a heuristic marker !docker:<12-hex-id> - the container ID truncated to 12 characters - and never the actual container name, because htop never queries the container runtime.

This commit adds real name resolution for Docker containers:

  • New linux/DockerMgr.{c,h}: a small unix-socket HTTP client that talks to /var/run/docker.sock, requests GET /containers//json, extracts the Name field, and caches results per container ID so the socket is hit at most once per container per htop run.
  • linux/LinuxProcessTable.c: when the cgroup heuristic produces a !docker: prefix, the ID is passed to DockerMgr and, on success, the real container name replaces the marker.
  • Display names are capped at 25 characters (the cache stores the truncated name), keeping the auto-width CONTAINER column reasonable with long container names.
  • linux/CGroupUtils.c: return empty string instead of / for processes not in a recognized container, keeping the column clean for host processes.

Failure behavior is intentional and graceful: if the Docker socket is unreachable (daemon stopped, socket missing, or user not in the docker group), DockerMgr_getContainerName() returns NULL, the call site keeps the original !docker: fallback, and nothing fails. No new crash paths are introduced.

Requires a reachable Docker daemon and a user in the docker group (or root). No new dependencies were added; build wiring is in Makefile.am (autotools-only tree). Verified with targeted unit tests (name resolution, 25-char cap, empty host-process column) against a live Docker daemon.

htop's CONTAINER column is derived purely from the cgroup scope name
(linux/CGroupUtils.c). For processes inside Docker containers it has
always shown a heuristic marker !docker:<12-hex-id> - the container ID
truncated to 12 characters - and never the actual container name,
because htop never queries the container runtime.

This commit adds real name resolution for Docker containers:

* New linux/DockerMgr.{c,h}: a small unix-socket HTTP client that talks
  to /var/run/docker.sock, requests GET /containers/<id>/json, extracts
  the Name field, and caches results per container ID so the socket is
  hit at most once per container per htop run.
* linux/LinuxProcessTable.c: when the cgroup heuristic produces a
  !docker: prefix, the ID is passed to DockerMgr and, on success, the
  real container name replaces the marker.
* Display names are capped at 25 characters (the cache stores the
  truncated name), keeping the auto-width CONTAINER column reasonable
  with long container names.
* linux/CGroupUtils.c: return empty string instead of / for processes
  not in a recognized container, keeping the column clean for host
  processes.

Failure behavior is intentional and graceful: if the Docker socket is
unreachable (daemon stopped, socket missing, or user not in the docker
group), DockerMgr_getContainerName() returns NULL, the call site keeps
the original !docker:<id> fallback, and nothing fails. No new crash
paths are introduced.

Requires a reachable Docker daemon and a user in the docker group (or
root). No new dependencies were added; build wiring is in Makefile.am
(autotools-only tree). Verified with targeted unit tests (name
resolution, 25-char cap, empty host-process column) against a live
Docker daemon.
@coderabbitai

coderabbitai Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

🧰 Additional context used
📚 Code guidelines (1)
docs/styleguide.md — configured
📝 Walkthrough

Walkthrough

The change adds Docker container-name lookup through /var/run/docker.sock, caches successful lookups, and uses resolved names for Docker identifiers in the Linux process table. Cgroup container filtering now returns an empty string when it produces no output.

Suggested reviewers: benbe

Priority: ⬇️ Low

Change: Feature

Merge Risk: 🟡 Moderate · up to 4e090

A stalled Docker connection can freeze container-name updates and the process-table refresh. Bound the socket exchange before merging; also correct the cache key so distinct containers cannot display the wrong name.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to 4e090

Container-name resolution now depends on Docker during process-table refresh. The change lacks request-identifier validation, bounded socket wait times, and exact container-identity matching in its cache. Ordinary lookup failures preserve the previous display, and only container names are exposed; privilege escalation or broad data disclosure has not been established.

Retained concerns

  • Medium · security · observed: The new lookup treats a naming-convention match as a Docker identifier and interpolates the extracted bytes into a socket request without character validation. This introduces a request boundary absent from the previous display-only behavior. Exploitation by a lower-authority process remains conditional on cgroup permissions and Docker request handling.
  • Medium · reliability · inferred: Synchronous Docker socket operations have no deadline. A stalled daemon or connection can therefore stall process-table refresh rather than reach the fallback, weakening containment of runtime failures and the availability of process visibility.
  • Low · architecture · observed: The shared cache uses only the identifier's hash as its identity key. Distinct identifiers with the same hash receive the first cached name without comparison of the original identifier, introducing cross-container misattribution in the display. No authorization decision based on that display has been established.
Security review details

Security Blast Radius

  • inferred — The demonstrated exposure concerns the htop instance, its displayed process identities, and the local Docker daemon accessible through its socket. A verified route to Docker write operations, full inspect-data disclosure, or wider service compromise has not been established.

Security Findings and Attack Paths

  • inferred — The injection candidate remains conditional: a less-authorized actor would need control of a matching visible cgroup label, htop would need Docker-socket access, and Docker would need to interpret the resulting request in a security-relevant way. Source establishes unchecked interpolation, but not those deployment and outcome prerequisites.

Trust Boundaries and Controls

  • observed — The lookup performs no privilege acquisition or identity switch; it uses the caller's available Unix-socket authority. The normal operation is a fixed GET, request and response storage are bounded, and only a capped name reaches the display. These controls limit the demonstrated outcome but do not validate request-target bytes.

Resilience and Maintainability Implications

  • observed — Ordinary socket, connection, write, empty-response, and parsing failures return NULL and retain the marker. Sockets are closed on the explicit failure and completion paths, cached strings are owned by the table, and callers receive copies. These ownership and fallback protections do not bound waiting or prevent hash-based identity aliasing.

Hardening Proposals

  • proposed — Validate the expected single Docker identifier before request construction, bound the total lookup duration so stalled operations reach the fallback, and retain exact identifiers alongside hashes for cache equality.
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A container name travels from the socket.
A cached result waits for its next lookup.
A leading slash slips away.
The process table reads the name.
Empty filters return empty.
Linux labels find their place.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 17c2cb39-ad31-47bd-bf74-63d24729579f
📥 Commits

Reviewing files that changed from the base of the PR and between d76b23e and 4e0902f.

📒 Files selected for processing (5)
  • Makefile.am
  • linux/CGroupUtils.c
  • linux/DockerMgr.c
  • linux/DockerMgr.h
  • linux/LinuxProcessTable.c

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Comment thread linux/DockerMgr.c
Comment on lines +29 to +34
static unsigned int hashStr(const char* s) {
unsigned int h = 5381;
while (*s)
h = (h * 33) ^ (unsigned char)*s++;
return h;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

sed -n '24,110p' linux/DockerMgr.c
rg -n 'typedef.*ht_key_t|Hashtable_new|Hashtable_put|Hashtable_get|Hashtable_create' Hashtable.* linux/DockerMgr.c

Repository: htop-dev/htop

Length of output: 3258


🏁 Script executed:

printf '%s\n' '--- Hashtable key and declarations ---'; cat -n Hashtable.h | sed -n '1,45p'; printf '%s\n' '--- Hashtable insertion and lookup ---'; cat -n Hashtable.c | sed -n '105,135p;205,250p;275,315p'; printf '%s\n' '--- Docker ID sources and cgroup parsing ---'; rg -n -i 'docker|container.*id|cgroup|containerId|container_id' linux | head -100

Repository: htop-dev/htop

Length of output: 13313


🏁 Script executed:

printf '%s\n' '--- Hashtable internals ---'; cat -n Hashtable.c | sed -n '30,105p;130,165p'; printf '%s\n' '--- Docker cgroup ID extraction ---'; cat -n linux/CGroupUtils.c | sed -n '205,280p'; printf '%s\n' '--- Cache caller ---'; cat -n linux/LinuxProcessTable.c | sed -n '1003,1018p'

Repository: htop-dev/htop

Length of output: 7254


🏁 Script executed:

printf '%s\n' '--- Hashtable insertion replacement ---'; cat -n Hashtable.c | sed -n '152,205p'; printf '%s\n' '--- Docker-specific filter branch ---'; cat -n linux/CGroupUtils.c | sed -n '25,75p;280,360p'

Repository: htop-dev/htop

Length of output: 6938


Use a collision-safe cache key for Docker IDs.

hashStr reduces each 12-character Docker ID to a 32-bit ht_key_t. Distinct IDs can therefore produce the same key. Hashtable_get compares only that key, so the second ID can receive the first ID's cached name.

Use a representation that preserves all 12 hexadecimal characters. Parsing into an integer requires widening ht_key_t beyond its current 32-bit type. Otherwise, retain the full ID and handle hash collisions before returning or replacing entries.

The owner-semantics warning does not apply: nameCache already uses Hashtable_new(16, true), and replacement values are freed under that setting.

Comment thread linux/DockerMgr.c
Comment on lines +42 to +90
static char* queryName(const char* id) {
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0)
return NULL;

struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", DOCKER_SOCKET);

if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
close(fd);
return NULL;
}

char req[256];
int reqLen = snprintf(req, sizeof(req),
"GET /containers/%s/json HTTP/1.1\r\n"
"Host: localhost\r\n"
"Accept: application/json\r\n"
"Connection: close\r\n\r\n", id);
if (reqLen <= 0 || reqLen >= (int)sizeof(req) || write(fd, req, (size_t)reqLen) != reqLen) {
close(fd);
return NULL;
}

char buf[RESPONSE_SIZE];
size_t used = 0;
ssize_t rd;
while (used < sizeof(buf) - 1 && (rd = read(fd, buf + used, sizeof(buf) - 1 - used)) > 0)
used += (size_t)rd;
close(fd);

if (used == 0)
return NULL;
buf[used] = '\0';

char* key = strstr(buf, "\"Name\":\"");
if (!key)
return NULL;
char* val = key + strlen("\"Name\":\"");
if (*val == '/')
val++;
char* end = strchr(val, '"');
if (!end)
return NULL;
return xStrndup(val, (size_t)(end - val));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

sed -n '1,125p' linux/DockerMgr.c
printf '\n-- socket timeout/nonblocking patterns --\n'
rg -n --glob '*.[ch]' 'SO_RCVTIMEO|SO_SNDTIMEO|poll\(|select\(|fcntl\(.*O_NONBLOCK|connect\(' linux | head -120

Repository: htop-dev/htop

Length of output: 3102


Bound the complete Docker socket exchange

queryName performs synchronous connect, write, and read calls during process-table refresh. A stalled Docker socket can delay the refresh indefinitely. Bound the complete exchange: use nonblocking connect with a finite poll deadline, and apply the same deadline to request writes and response reads. SO_RCVTIMEO and SO_SNDTIMEO alone do not bound connect.

The stack-buffer and negative-cache suggestions are separate concerns and are omitted from this correction.

@BenBE

BenBE commented Oct 3, 2026

Copy link
Copy Markdown
Member

Two more points:

  1. Has AI been used while creating this PR? Cf. styleguide and AI contribution rules for details.
  2. Resolution of container names that can block should be handled async to minimize impact on process list refreshes. And yes, this means, that for a certain timeframe the unresolved names might appear, until they are later replaced by their resolved variants.

Overall not a huge fan; but TBH not a huge fan of Docker either …

@BenBE BenBE added Linux 🐧 Linux related issues feature request Completely new feature requested labels Oct 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature request Completely new feature requested Linux 🐧 Linux related issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants