This is the mail archive of the
libc-alpha@sourceware.org
mailing list for the glibc project.
What to do when glibc environment variables or tunables conflict?
- From: Carlos O'Donell <carlos at redhat dot com>
- To: libc-alpha <libc-alpha at sourceware dot org>, Szabolcs Nagy <szabolcs dot nagy at arm dot com>, Siddhesh Poyarekar <siddhesh at gotplt dot org>, Joseph Myers <joseph at codesourcery dot com>
- Cc: Florian Weimer <fweimer at redhat dot com>
- Date: Wed, 5 Feb 2020 17:12:01 -0500
- Subject: What to do when glibc environment variables or tunables conflict?
Explicit use of LD_BIND_NOW=1 or LD_DEBUG=unused disables ld audit
because both uses set GLRO(dl_lazy) to zero and disable lazy binding
entirely. The ld audit interface requires the ld lazy binding hook
to perform various auditing activities. In the future, after refactoring,
we should be able to support all of LD_AUDIT with the exception of PLT
enter/exit (which has questionable value).
What should happen here?
Which env vars should override other env vars?
Should env vars override options built into a binary?
For example if the auditor is enabled via DT_AUDIT (a feature which
we are going to add) should LD_BIND_NOW or LD_DEBUG=unused disable it?
I would like to suggest the following axioms:
(A1) Consistent results should be produced regardless of env var order
or tunable order.
(A2) Where multiple variables conflict we should pick a winner and consider
hard failing where users require a specific semantic behaviour.
(A3) Where conflict exists the dynamic loader prints a warning for an env var
that is ignored e.g. clear and consistent visibility for users.
(A4) Give preference to any executable-encoded choices over environment variables.
Picking a winner:
Notes:
- When "print a warning" is written this can mean that we only print something
if LD_DEBUG=all is set or something that matches the warnings we would like
to print.
(a) Auditing is functionally required since it can modify load search
paths and symbol binding for applications.
(b) Immediate binding is generally an optimization or a workaround.
- Enabled unconditionally by some architectures for certain symbol types
e.g. AArch64 VPCS, where this is a compromise between ABI and
performance. The use of any env var or executable-encoded option should
not disable this architecture-specific choice.
There are 5 state variables:
LD_DEBUG=unused / LD_DEBUG=
LD_BIND_NOW=1 / LD_BIND_NOW=
LD_AUDIT=... / LD_AUDIT=
DT_AUDIT / No DT_AUDIT
DT_FLAGS BIND_NOW / No DT_FLAGS BIND_NOW
Which is 32 states, but we can collapse the LD_DEBUG state because it is
a debug state. All subsequent states, 16 of them, are listed below, some
of which conflict and some which don't.
(1) LD_DEBUG=unused / <All other states>, LD_DEBUG= / <All other states>
LD_DEBUG=unused should print a warning and be disabled if BIND_NOW
(builtin or env var) or ld audit is in use (builtin or env var)
(2) LD_BIND_NOW=1 / LD_AUDIT= / DT_AUDIT / No DT_FLAGS BIND_NOW
LD_BIND_NOW=1 should print a warning and be disabled if DT_AUDIT is
specified in the binary or any initially loaded shared objects.
- Requires you inspect dynsym of all loaded objects in dep tree.
(3) LD_BIND_NOW=1 / LD_AUDIT= / No DT_AUDIT / No DT_FLAGS BIND_NOW
LD_BIND_NOW=1 / LD_AUDIT= / No DT_AUDIT / DT_FLAGS BIND_NOW
LD_BIND_NOW=1 should work as expected if the binary or any initially
loaded shared objects don't have DT_AUDIT.
- If DT_AUDIT appears in an object that is dlopen'd then we should fail
to load such an object since we can't honour the encoded request.
(4) Conflict:
LD_BIND_NOW= / LD_AUDIT= / DT_AUDIT / DT_FLAGS BIND_NOW
LD_BIND_NOW= / LD_AUDIT=... / DT_AUDIT / DT_FLAGS BIND_NOW
LD_BIND_NOW=1 / LD_AUDIT= / DT_AUDIT / DT_FLAGS BIND_NOW
LD_BIND_NOW=1 / LD_AUDIT=... / DT_AUDIT / DT_FLAGS BIND_NOW
No conflict:
LD_BIND_NOW= / LD_AUDIT= / No DT_AUDIT / No DT_FLAGS BIND_NOW
LD_BIND_NOW= / LD_AUDIT= / No DT_AUDIT / DT_FLAGS BIND_NOW
LD_BIND_NOW= / LD_AUDIT= / DT_AUDIT / No DT_FLAGS BIND_NOW
If -Wl,-z,now and -Wl,--audit,AUDITLIB are used together then the
dynamic loader should honour DT_AUDIT and print a warning that
BIND_NOW is disabled (see (a)).
(5) LD_BIND_NOW= / LD_AUDIT=... / No DT_AUDIT / DT_FLAGS BIND_NOW
LD_BIND_NOW=1 / LD_AUDIT=... / No DT_AUDIT / DT_FLAGS BIND_NOW
LD_AUDIT and DT_FLAGS BIND_NOW are used together then the dynamic
loader should honour BIND_NOW and print a warning that LD_AUDIT is
disabled (see (A4)).
(6) Conflict:
LD_BIND_NOW=1 / LD_AUDIT=... / No DT_AUDIT / No DT_FLAGS BIND_NOW
LD_BIND_NOW=1 / LD_AUDIT=... / DT_AUDIT / No DT_FLAGS BIND_NOW
No conflict:
LD_BIND_NOW= / LD_AUDIT=... / No DT_AUDIT / No DT_FLAGS BIND_NOW
LD_BIND_NOW= / LD_AUDIT=... / DT_AUDIT / No DT_FLAGS BIND_NOW
LD_AUDIT and no DT_FLAGS BIND_NOW during initial startup should allow
ld audit to operate.
- If subsequent dlopen requires BIND_NOW. We should load the requested
object but print a warning that BIND_NOW is disabled due to auditing.
- If LD_BIND_NOW=1 is specified we should print a warning that it is
ignored.
Notes:
- When LD_AUDIT and DT_AUDIT are specified the list of auditors is the
concatenation of both auditor lists.
- Given that LD_AUDIT is rare, and that when present it's usually a required
feature for the purposes of auditing or search path manipulation, we pick
it over LD_BIND_NOW / DT_FLAGS BIND_NOW. It would seem like we shouldn't
because LD_BIND_NOW is important for security, but in the vast majority of
the cases we should be able to easily enable BIND_NOW without conflict with
an auditor. We pick the auditor because it may have an even larger security
impact like disallowing binaries from certain paths to be loaded e.g.
production vs. debug shared objects.
--
Cheers,
Carlos.