|
|
Subscribe / Log in / New account

Distribution of security fixes

The LD_DEBUG environment variable is one of those obscure, useful features found in glibc. By setting LD_DEBUG to one of a few specific values (use help to get the full list), you can get a great deal of information on just how the dynamic library loader is resolving symbols and performing relocation. This information can be most useful for tracking down certain kinds of obscure shareable library problems.

LD_DEBUG can be verbose; it can also provide information about security-critical programs - especially those running setuid - which perhaps should not be made available to just anybody. The large amount of output created by LD_DEBUG can also be used as a sort of poor-man's single-stepping mechanism. If you can control when the standard output will block, you can stop a setuid program at almost any library call. This capability can be most useful if you are trying to exploit a difficult race condition, such as a temporary file vulnerability. The ability to stop a program at an arbitrary point can turn a small, difficult window into a wide-open one which can be exploited at leisure.

Thus, it would make sense to disallow LD_DEBUG for setuid binaries. Unfortunately, this didn't occur to the glibc implementors, who did not add any checks for setuid operation in the LD_DEBUG code. Gentoo has recently issued an update fixing the problem; no other distributors have followed suit as of this writing.

As it turns out, some distributors do not need to. OpenWall fixed this problem over three years ago; ALT Linux also patched glibc in its distribution. Somehow, however, the fixes applied by these distributors never got into wider distribution.

This is not the first time that somebody has discovered a security problem for which a fix had been available for years. These incidents are, at best, a missed opportunity: known holes with available fixes remain unpatched for long periods of time. A less pleasant possibility is that crackers can look at the patches applied by security-conscious distributions (such as OpenWall) in search of holes which have not been fixed elsewhere. Security fixes are best applied universally.

The obvious way to ensure widespread diffusion of security fixes is to submit them back to the package's maintainer. Such patches should almost always be accepted - or the maintainer should come up with a better way to fix the problem. If the maintainer refuses to fix the problem, there is always the time-honored technique of posting an advisory to Bugtraq. What should not be an option is keeping security fixes to ones self.


to post comments

Distribution of security fixes

Posted Aug 26, 2004 2:13 UTC (Thu) by jreiser (subscriber, #11027) [Link] (2 responses)

Unfortunately, this didn't occur to the glibc implementors, who did not add any checks for setuid operation in the LD_DEBUG code.

Disabling such a check is early on malware's list of things to do, and it is easy: for instance, replace one conditional branch instruction with a NOP. If security really is an important goal, then SUID executables deserve their own separate libraries, or no libraries at all. And there should be a kernel flag option to mmap()/mprotect() which says, "No changes allowed to this vma, except deletion at exit()."

What should not be an option is keeping security fixes to ones self.

That depends on the economy-of-the-day. Sometimes exclusive knowledge of a vulnerability, how to exploit it, and/or how to fix it, is worth much more than what is available in the "gift economy," particularly in the short term.

Distribution of security fixes

Posted Aug 26, 2004 3:42 UTC (Thu) by Ross (guest, #4065) [Link] (1 responses)

If they are able to edit the library on disk or in RAM then the game is
already over. I fail to understand your point.

Distribution of security fixes

Posted Aug 26, 2004 14:42 UTC (Thu) by jreiser (subscriber, #11027) [Link]

A successful exploit might depend on the rate and amount of change from "good" to bad memory image: the smaller the number of bits that must be changed, and the smaller their range of addresses, sometimes can make it easier to accomplish.

Distribution of security fixes

Posted Aug 26, 2004 11:41 UTC (Thu) by rwmj (subscriber, #5474) [Link] (7 responses)

Unfortunately the maintainer of glibc isn't very receptive to problems with LD_* environment variables and setuid or other processes.

For example, for the longest time, having an empty path in LD_LIBRARY_PATH means that the current directory is searched for libraries. eg. If LD_LIBRARY_PATH accidentally contains :/usr/local/lib then the current directory is always checked for libraries before any other directory.

This happens because sys admins do things like:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib

and put it in the system-wide profile scripts (note what happens if LD_LIBRARY_PATH is empty to begin with). If you have a sysadmin who's made this simple mistake, then it's trivial to take over the system - create a library like libtermcap.so containing trojan code, drop it into /tmp, and just wait. As soon as anyone types 'ls' in /tmp, they're running your code.

There's a simple and obvious fix - ignore empty elements in LD_LIBRARY_PATH. If the sysadmin is stupid enough to want the current directory searched, let them specify a library path of "." explicitly.

The maintainer of glibc does not see this bug as a bug which requires fixing. Instead he would prefer all sysadmins in the world to vet their profile scripts carefully.

Rich.

Distribution of security fixes

Posted Aug 26, 2004 19:16 UTC (Thu) by jreiser (subscriber, #11027) [Link] (1 responses)

Other lessons: Syntax with terminators tends to be more secure than syntax with separators, because 1:1 has no boundary case that n:(n-1) has. The shell could offer builtin procedures for appending and pre-pending to separated lists.

Distribution of security fixes

Posted Sep 2, 2004 12:47 UTC (Thu) by ingvar (guest, #1530) [Link]

It (sort-of) exists in PSIX-compliant shells...

FOO=${FOO+$FOO:}/usr/local/lib # should do the trick

ingvar@gruk$ FOO=${FOO+$FOO:}/usr/local/lib
ingvar@gruk$ echo $FOO
/usr/local/lib
ingvar@gruk$ FOO=${FOO+$FOO:}/evil/cracker/path
ingvar@gruk$ echo $FOO
/usr/local/lib:/evil/cracker/path

Distribution of security fixes

Posted Aug 27, 2004 14:24 UTC (Fri) by jeremiah (subscriber, #1221) [Link] (1 responses)

That's just beautiful man. I'll have to check some of my systems out now, but that's alright. That has to be the slickest flaw I've heared about in quite sometime.

Does anyone know of a place to find a list of features similar to this in type. Ie one's that a sysadmin should logically be able to create without worry, yet are completely henious? I've been doing Linux for 12 years, and have never heard of this, and I wonder where I should go to find more like it, since this is not the type of stuff to show up in most advisories.

FYI lwn staff, I'd pay for gems like this. Ie a long standing list of potential exploits that cannot be fixed by patching.

Distribution of security fixes

Posted Aug 27, 2004 14:36 UTC (Fri) by jeremiah (subscriber, #1221) [Link]

Just a follow up question, What's a nice tool for checking system security from the inside. Most tools I use either check from the outside (nessus et al) or monitor the state of the system inside (logwatch/tripwire etc.) Bastile will harden the system, but I'd like a report based thing, so that I can choose the vulnerabilites I'd like to keep as well as fix the others in a way that matches our infrastructure.

Distribution of security fixes

Posted Aug 27, 2004 22:17 UTC (Fri) by EricBackus (guest, #2816) [Link] (1 responses)

Note that there's a similar problem with $PATH. Doing:
    export PATH=$PATH:/usr/local/bin
could possibly give you an empty component in PATH, which we all know is a bad thing. Now, an empty PATH variable is probably less common than an empty LD_LIBRARY_PATH, but you never know...

Long ago, I wrong an "add_to_path" shell function, which only adds a path if the path exists and does the right thing if the current path is empty. It's not TOO complicated:

# Add a directory to PATH if it exists and is not already in PATH.
# PATH is $1, Directory is $2.  If PATH does not exist, don't prepend
# a ":" since that adds the current directory to it.  If optional $3
# is equal to "atstart", add the new component to the start of PATH,
# otherwise add it at end.
function add_to_path
{
    if [ -d "$2" ]; then
        p=`eval echo \"\\$$1\"`
        echo "$p" | grep -- "$2" > /dev/null
        if [ "$?" -ne 0 ]; then
            if [ -z "$p" ]; then
                eval $1=\""$2"\"
            else
                if [ "$3" = "atstart" ]; then
                    eval $1=\""$2:\$$1"\"
                else
                    eval $1=\""\$$1:$2"\"
                fi
            fi
        fi
    fi
}
With that in place, your shell script can do:
    add_to_path PATH /usr/local/bin
    add_to_path LD_LIBRARY_PATH /usr/local/lib
    add_to_path MANPATH /usr/local/man
And you ensure that things work correctly and efficiently.

Distribution of security fixes

Posted Sep 5, 2004 16:22 UTC (Sun) by k8to (guest, #15413) [Link]

Maybe you really like your script, but it seems problemed to me. It's possible to want a dir to a path which does not currently exist. Also, it will not work on some versions of sh which do not support the 'function keyword'.

Pesonally I'd seperate add_to_path from add_to_manpath to produce the simple:

add_to_path() {
if [ "$PATH"x == x ]; then PATH=$1; fi
if [ "$2" == atstart ]; then PATH=$1:$PATH
else; PATH=$PATH:$1; fi
}
But I suppose, each to his own.

Distribution of security fixes

Posted Sep 6, 2004 3:10 UTC (Mon) by zone (guest, #3633) [Link]

I was about to point out that this is a requirement of SUS, but it turns out even SUSv2 regards this as a legacy feature. From SUSv3:

PATH
[...]
A zero-length prefix is a legacy feature that indicates the current working directory. It appears as two adjacent colons ( "::" ), as an initial colon preceding the rest of the list, or as a trailing colon following the rest of the list. A strictly conforming application shall use an actual pathname (such as .) to represent the current working directory in PATH .
[...]

Stopping the program?

Posted Aug 26, 2004 13:55 UTC (Thu) by kweidner (guest, #6483) [Link] (4 responses)

The ability to stop a program at an arbitrary point can turn a small, difficult window into a wide-open one which can be exploited at leisure.

I can't see how this is supposed to happen - as far as I can tell LD_DEBUG generates all its output in the dynamic linking stage of program execution, which is not intermingled with the normal program execution. The symbols don't get resolved one by one as they get used. If a program dynamically loads additional shared libraries at runtime, that's where you could cause a delay, but that doesn't appear to result in anything exploitable other than perhaps a denial of service attack.

Also, the original advisory claimed that the address information printed could be used to replace and subvert library functions, something that LD_DEBUG doesn't offer any mechanism for.

In general, the entire LD_DEBUG issue doesn't appear to be an exploitable security hole, and is only a harmless information leak about information that's likely to be public anyway. If you want to know how a specific SUID binary works, you can usually find out by trying it yourself by running it on your own system, using the same Linux distribution.

All "exploits" mentioned so far require a completely separate security hole that would be sufficient in itself to subvert security, even without using LD_DEBUG.

    -Klaus

Stopping the program?

Posted Aug 26, 2004 19:32 UTC (Thu) by jreiser (subscriber, #11027) [Link] (3 responses)

... the dynamic linking stage of program execution, which is not intermingled with the normal program execution.

False. Nearly all dynamic symbol resolution is done with the equivalent of RTLD_LAZY, which is on-demand. The flags DT_BIND_NOW, DF_BIND_NOW, and DF_1_NOW are used rarely.

The symbols don't get resolved one by one as they get used.

False. That is exactly how they get resolved nearly all the time.

... the address information printed could be used to replace and subvert library functions.

Yes, getting the addresses can help pinpoint where to attack. The address may vary by library build (Linux distribution) or mmap() randomization.

In general, the entire LD_DEBUG issue doesn't appear to be an exploitable security hole, ...

The ability to block the process (by blocking the output stream from LD_DEBUG) widens the window during which an existing time-dependent weakness may be exploited.

a completely separate security hole that would be sufficient in itself to subvert security

Sometimes holes are time-dependent, and the ability to "stop the clock" can make a 1 millisecond window into eternity.

Stopping the program?

Posted Aug 26, 2004 20:12 UTC (Thu) by kweidner (guest, #6483) [Link] (2 responses)

False. Nearly all dynamic symbol resolution is done with the equivalent of RTLD_LAZY, which is on-demand. The flags DT_BIND_NOW, DF_BIND_NOW, and DF_1_NOW are used rarely.

I stand corrected and apologize for spreading misinformation. Is the decision to use RDLT_LAZY hardcoded in /lib/ld-linux.so.2 ? Maybe it would make sense to change that to the equivalent of RTLD_NOW for SUID apps for more deterministic behavior, since the delay caused by LD_DEBUG abuse only increases the already present delay for dynamic symbol resolution in the middle of an operation.

-Klaus

Stopping the program?

Posted Aug 26, 2004 22:55 UTC (Thu) by jreiser (subscriber, #11027) [Link]

RTLD_LAZY is the specified default behavior, and in that sense is hardcoded into ld-linux.so.2. Changing ld-linux.so.2 to use RTLD_NOW for SUID executables seems like a good choice in general. In most cases the intent for an executable to be run SUID is known at build time, so the build itself could put DT_BIND_NOW into the DYNAMIC section, which would achieve the desired effect with no changes required to ld-linux.so.2.

Stopping the program?

Posted Nov 20, 2004 2:59 UTC (Sat) by bluefoxicy (guest, #25366) [Link]

Maybe it would make sense to change that to the equivalent of RTLD_NOW for SUID apps for more deterministic behavior, since the delay caused by LD_DEBUG abuse only increases the already present delay for dynamic symbol resolution in the middle of an operation.

I think that such a patch would be a good idea; RDLT_LAZY looks to me to be less intrusive at runtime, as sporadic minor delays are not easily noticed, while a large chain of such delays are very visible. The targetting of specific entry points (SUID binaries) rather than the entire system (firefox, thunderbird, gnome, X) would be a good way to give the performance benefits of lazy back to the user, which should always be a secondary concern once major security issues are handled.

A few googles right now and a breeze through the source (which confused me and gave me no clue wtf is going on) don't show any obvious indication that current behavior forces RTLD_NOW on SUID or SGID binaries. There are two potential solutions to this issue.

  1. Write a patch

    Writing a patch may be the most expedient way to handle this issue, as the glibc people don't seem to have an immediate interest in fixing this, based on the observation that this idea has been out for an adequate time period.

  2. Band together and badger the glibc maintainers until they patch glibc

    If nobody else is going to do it, they have to. This may also be required in conjunction with (1), as the glibc team may not want to actually adopt the patch even if somebody else writes it for them.

Well, I'm out for (1) at least; I can't even understand the code, much less patch it.


Copyright © 2004, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds