guides / ldd-dangers-and-alternatives toolingupdated 2026-10-25 readelf -h ./app

ldd is not safe to run on an untrusted binary — use these instead

2026-10-25 By luckyray Debian 12 · Ubuntu 24.04 · Alpine 3.20

ldd is the fastest way to see whether a binary’s libraries resolve, and it is in every “how do I debug a missing .so” answer on the internet. It is also, by design, able to run the binary you point it at. On a file you did not build, that is an unacceptable trade.

What ldd actually is

$ file $(command -v ldd)
/usr/bin/ldd: Bourne-Again shell script, ASCII text executable

It is a shell script. Historically it set LD_TRACE_LOADED_OBJECTS=1 and executed the target, which made the loader print the dependency list instead of running the program. On glibc systems the script now tries ld.so --list first, which does not execute the program — but the script still contains fallback paths, and on other libcs and older systems the exec happens.

Two concrete consequences:

  • A malicious binary can run code at ldd time on systems where the fallback path is taken. It can also simply be a shell script, which ldd will happily execute.
  • ldd on a static binary prints not a dynamic executable, which people misread as an error.

The man page states it directly: “ldd should never be employed on an untrusted executable, since this may result in the execution of arbitrary code.”

Read-only alternative 1: objdump -p

$ objdump -p ./app | grep NEEDED
  NEEDED               libssl.so.1.1
  NEEDED               libc.so.6

objdump parses the dynamic section. It never loads, maps or executes anything. This is the recommended replacement in the glibc documentation and it works on any ELF, including ones whose architecture does not match the host.

Read-only alternative 2: readelf -d

$ readelf -d ./app | grep -E 'NEEDED|RPATH|RUNPATH'
 0x0000000000000001 (NEEDED)   Shared library: [libssl.so.1.1]
 0x0000000000000001 (NEEDED)   Shared library: [libc.so.6]
 0x000000000000001d (RUNPATH)  Library runpath: [$ORIGIN/../lib]

Strictly better than objdump -p for this purpose, because it also prints the search paths. Both read only.

Read-only alternative 3: resolve it yourself

ldd really answers two questions at once: what does the binary need, and what would resolve on this host. Split them:

# what it needs (read-only)
readelf -d ./app | awk '/NEEDED/ {print $NF}' | tr -d '[]'

# what the host can provide (index lookup, no execution)
ldconfig -p | grep -E 'libssl\.so\.1\.1|libc\.so\.6'

Comparing those two lists gives you the same answer as ldd, using only index reads.

When you do want loader behaviour

If you need the loader’s actual search decisions — including RUNPATH ordering, which is where most surprises live — invoke the loader explicitly rather than through the wrapper:

/lib64/ld-linux-x86-64.so.2 --list ./app

--list is a loader option, not an execution mode, and the loader is the component that would have been started anyway. Understand the caveat: on a foreign-architecture binary this fails, because the loader itself is architecture-specific.

For the search trace, LD_DEBUG=libs is the tool that ends arguments:

$ LD_DEBUG=libs ./app 2>&1 | grep -A2 'libssl'
     21490:	find library=libssl.so.1.1 [0]; searching
     21490:	  trying file=/usr/lib/x86_64-linux-gnu/libssl.so.1.1
     21490:	search path=/usr/lib/x86_64-linux-gnu  (system search path)

This executes the program, so it is for binaries you trust and environments you control.

What to do in practice

SituationUse
Binary from a vendor or the internetreadelf -d / objdump -p only
Binary you built, on your machineldd is fine, LD_DEBUG=libs for ordering
Different architecture from the hostreadelf -d — nothing else can run
Need to know if this host resolves itldconfig -p + the NEEDED list

The Dependency Doctor does the first two columns in a browser: it parses DT_NEEDED, RPATH and RUNPATH from the file without executing it, and if you paste your ldd output it maps resolved and missing entries onto that list — so you get the diagnosis without running the loader on an unknown file.

Related: missing shared libraries for the search order in detail, and musl vs glibc for the interpreter mismatch that ldd itself cannot report.


Every command in this guide was run against the distribution named in the output. If your output differs, the mechanism section explains why — and the ELF Inspector shows the same fields without needing a shell.

Related

guides
  1. cannot execute binary file: Exec format error — causes and fixes Why Linux refuses to run a binary with Exec format error, how to confirm the cause in three commands, and the five fixes that actually apply.
  2. version `GLIBC_2.34' not found — why copying libc never works How glibc symbol versioning works, why a binary built on a newer distribution refuses to start on an older one, and the four legitimate fixes.
  3. How to tell whether a binary is stripped (and what it costs you) What strip removes, what it leaves behind, how to confirm it with file and readelf, and how to get usable stack traces back from a stripped production binary.