What to do when glibc environment variables or tunables conflict?

classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

What to do when glibc environment variables or tunables conflict?

Carlos O'Donell-6
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.

Reply | Threaded
Open this post in threaded view
|

Re: What to do when glibc environment variables or tunables conflict?

Siddhesh Poyarekar-8
On 06/02/20 03:42, Carlos O'Donell wrote:
> 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

LD_DEBUG doing that is a bug.  If th binary is built with lazy binding
(and LD_BIND_NOW is not set) it should simply warn that 'unused' is
unavailable for 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.
>

Agreed, this would mean maintaining and documenting a hierarchy of
tunables/envvars in the same namespace.  Tunables in different
namespaces ought to not conflict, but I haven't looked at all of them to
authoritatively state that.

> (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.

Agreed.

> 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.

Agreed, with emphasis that we should not print anything from the dynamic
linker unless absolutely necessary.

To expand on LD_DEBUG, it should be the lowest priority.  That is,
LD_DEBUG should give you all kinds of diagnostic messages if you request
them, but it will not get in the way of any other feature enabled either
via the environment or through build flags.  It must not change program
(or program loading) behaviour.

For DF_BIND_NOW vs DT_AUDIT, the following rules seem obvious assuming
that we generally want auditing to be able to override immediate binding
and they more or less align with yours:

1. The static linker should not allow DT_AUDIT and DF_BIND_NOW on the
same object.  Print an error and force the user to choose one or the other.

2. The built-in option wins over envvars

3. If both LD_BIND_NOW and LD_AUDIT are provided, choose auditing.
Print a warning if LD_DEBUG is also set

I'm not clear however whether you intend to have DT_AUDIT impact the
entire program or just the DSO in question.  That is, would one DSO
having DT_AUDIT result in auditing of all linker activities for all DSOs
or for just the DSO that is linked with DT_AUDIT.  Also what about cases
where different DSOs are linked with different DT_AUDIT lists?  Finally,
what do their linker namespaces look like, are they distinct from each
other or do they get merged into a single namespace, distinct from the
default namespace?

Some of those answers may affect the extent to which we can or cannot do
lazy binding alongside DT_AUDIT.

Siddhesh
Reply | Threaded
Open this post in threaded view
|

Re: What to do when glibc environment variables or tunables conflict?

Andreas Schwab
On Feb 06 2020, Siddhesh Poyarekar wrote:

> On 06/02/20 03:42, Carlos O'Donell wrote:
>> 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
>
> LD_DEBUG doing that is a bug.

I disagree.  LD_DEBUG=unused is for probing, and doesn't even run the
object.  So auditing doesn't come into play.

> To expand on LD_DEBUG, it should be the lowest priority.

No, it should be highest priority.  It's a debugging device, so it needs
to be honored to be useful.

Andreas.

--
Andreas Schwab, SUSE Labs, [hidden email]
GPG Key fingerprint = 0196 BAD8 1CE9 1970 F4BE  1748 E4D4 88E3 0EEA B9D7
"And now for something completely different."
Reply | Threaded
Open this post in threaded view
|

Re: What to do when glibc environment variables or tunables conflict?

Siddhesh Poyarekar-8
On 06/02/20 14:49, Andreas Schwab wrote:

> On Feb 06 2020, Siddhesh Poyarekar wrote:
>
>> On 06/02/20 03:42, Carlos O'Donell wrote:
>>> 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
>>
>> LD_DEBUG doing that is a bug.
>
> I disagree.  LD_DEBUG=unused is for probing, and doesn't even run the
> object.  So auditing doesn't come into play.

Ahh, I stand corrected for this.  But in that case it should not get in
the way of LD_AUDIT, should it?

>> To expand on LD_DEBUG, it should be the lowest priority.
>
> No, it should be highest priority.  It's a debugging device, so it needs
> to be honored to be useful.

The context of my comment is when LD_DEBUG changes behaviour and
conflicts with other options.

Siddhesh
Reply | Threaded
Open this post in threaded view
|

Re: What to do when glibc environment variables or tunables conflict?

Andreas Schwab
On Feb 06 2020, Siddhesh Poyarekar wrote:

> On 06/02/20 14:49, Andreas Schwab wrote:
>> On Feb 06 2020, Siddhesh Poyarekar wrote:
>>
>>> On 06/02/20 03:42, Carlos O'Donell wrote:
>>>> 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
>>>
>>> LD_DEBUG doing that is a bug.
>>
>> I disagree.  LD_DEBUG=unused is for probing, and doesn't even run the
>> object.  So auditing doesn't come into play.
>
> Ahh, I stand corrected for this.  But in that case it should not get in
> the way of LD_AUDIT, should it?

Do you know a better way to implement LD_DEBUG=unused?

Andreas.

--
Andreas Schwab, SUSE Labs, [hidden email]
GPG Key fingerprint = 0196 BAD8 1CE9 1970 F4BE  1748 E4D4 88E3 0EEA B9D7
"And now for something completely different."
Reply | Threaded
Open this post in threaded view
|

Re: What to do when glibc environment variables or tunables conflict?

Siddhesh Poyarekar-8
On 06/02/20 17:18, Andreas Schwab wrote:
>> Ahh, I stand corrected for this.  But in that case it should not get in
>> the way of LD_AUDIT, should it?
>
> Do you know a better way to implement LD_DEBUG=unused?

No my point is that it should not affect LD_AUDIT or DT_AUDIT if it
doesn't actually run the binary; rather if it does, then maybe that's
something we need to address without disabling debug, since with with
'unused' the debugging is the primary operation in question.

I'm not disagreeing with you, I'm circling back to the original point of
the LD_AUDIT vs LD_DEBUG conflict.

Siddhesh