KSR[T] Advisory #2: ld.so

KSR[T] (ksrt@DEC.NET)
Thu, 17 Jul 1997 17:15:21 -0700

KSR[T] Advisory #002
Date: Jul 16, 1997
ID #: lin-ldso-002

Operating System(s): Linux

Affected Program: ld.so / ld-linux.so

Problem Description: ld.so is the run-time linker used by dynamically linked
executables(a.out). Inside the error reporting function
there is a call to vsprintf, which doesn't check the size
of the string it is storing in an automatic buffer.

The ELF version of run-time linker(ld-linux.so) is
vulnerable to an almost identical stack overwrite.

Compromise: A local user that can execute any dynamically linked
setuid binary and can force ld.so to error, can execute
arbitrary code as root.

Patch/Fix: Upgrade your ld.so to the latest version, or apply the
patch below provided by Alan Cox.

---- cut here ----
--- ld.so-1.7.14/d-link/boot1.c Thu Dec 14 19:08:19 1995
+++ ld.so-1.7.14-fixed/d-link/boot1.c Wed Jul 16 15:37:11 1997
@@ -108,10 +108,12 @@
#include "syscall.h"
#include "string.h"

+#define ELF_LDSO_IMAGE "/lib/ld-linux.so.1"
+
static char * _dl_malloc_addr, *_dl_mmap_zero;
char * _dl_library_path = 0; /* Where we look for libraries */
char *_dl_preload = 0; /* Things to be loaded before the libs. */
-char *_dl_progname = "/lib/ld-linux.so.1";
+char *_dl_progname = ELF_LDSO_IMAGE;
static char * _dl_not_lazy = 0;
static char * _dl_warn = 0; /* Used by ldd */
static char * _dl_trace_loaded_objects = 0;
@@ -165,6 +167,45 @@
#endif

/*
+ * Stop argv0 overflowing vsprintf, but also try to stop false positives
+ * We obey the following rule
+ *
+ * If namesize < 256 keep
+ * If name from last / < 256 use that
+ * else use ELF_LDSO_IMAGE
+ *
+ * This ensures /very/long/stupid/nfs/path/we/often/get/foobarcmd
+ * comes out at least as.
+ *
+ * foobarcmd: someerror
+ *
+ * Even if we fix vsprintf to be vsnprintf (which we should), this
+ * ought to be kept to help make real size limited errors clearer.
+ */
+
+static char *argv_remap(char *ptr)
+{
+ char *tmp;
+ if(strlen(ptr)<256)
+ return ptr;
+ if(!*ptr)
+ return ptr;
+ tmp=ptr+strlen(ptr)-1;
+ /*
+ * Walk back down the chain until we find a slash
+ */
+ while(tmp>=ptr && *tmp!='/')
+ tmp--;
+ /*
+ * No slash, or too long after slash and Im not playing so nyah
+ */
+ if(*tmp!='/')
+ return ELF_LDSO_IMAGE;
+ if(strlen(tmp)>256) /* Not off by 1 .. strlen includes the / */
+ return ELF_LDSO_IMAGE;
+ return tmp+1;
+}
+/*
* This stub function is used by some debuggers. The idea is that they
* can set an internal breakpoint on it, so that we are notified when the
* address mapping is changed in some way.
@@ -487,7 +528,7 @@
}

if (argv[0])
- _dl_progname = argv[0];
+ _dl_progname = argv_remap(argv[0]);

/* Now we need to figure out what kind of options are selected.
Note that for SUID programs we ignore the settings in LD_LIBRARY_PATH */
--- ld.so-1.7.14/ld-so/ld.so.c Tue Nov 14 19:15:02 1995
+++ ld.so-1.7.14-fixed/ld-so/ld.so.c Tue Jun 24 10:55:54 1997
@@ -151,6 +151,46 @@
}
#endif

+/*
+ * Stop argv0 overflowing vsprintf, but also try to stop false positives
+ * We obey the following rule
+ *
+ * If namesize < 256 keep
+ * If name from last / < 256 use that
+ * else use LDSO_NAME
+ *
+ * This ensures /very/long/stupid/nfs/path/we/often/get/foobarcmd
+ * comes out at least as.
+ *
+ * foobarcmd: someerror
+ *
+ * Even if we fix vsprintf to be vsnprintf (which we should), this
+ * ought to be kept to help make real size limited errors clearer.
+ */
+
+static char *argv_remap(char *ptr)
+{
+ char *tmp;
+ if(strlen(ptr)<256)
+ return ptr;
+ if(!*ptr)
+ return ptr;
+ tmp=ptr+strlen(ptr)-1;
+ /*
+ * Walk back down the chain until we find a slash
+ */
+ while(tmp>=ptr && *tmp!='/')
+ tmp--;
+ /*
+ * No slash, or too long after slash and Im not playing so nyah
+ */
+ if(*tmp!='/')
+ return LDSO_IMAGE;
+ if(strlen(tmp)>256) /* Not off by 1 .. strlen includes the / */
+ return LDSO_IMAGE;
+ return tmp+1;
+}
+
void
shared_loader(int func, ...)
{
@@ -207,12 +247,14 @@
save_mapinfo(mapinfo);
#endif
argv0 = va_arg(ap, char *);
+ argv0 = arg_remap(argv0);
__environ = va_arg(ap, char **);
__SHARED_LIBRARIES__ = va_arg(ap, struct libentry **);
_SHARABLE_CONFLICTS__ = va_arg(ap, struct fixuplist *);
if (func == FUNC_LINK_AND_CALLBACK)
callback = va_arg(ap, callbackptr);
va_end(ap);
+
break;
default:
/* you want me to do what? */
@@ -228,7 +270,8 @@
/* find out who we are, in case somebody wants to know */
if (!argv0 && !(argv0 = getenv(LDD_ARGV0)))
argv0 = LDSO_IMAGE;
-
+ argv0=argv_remap(argv0);
+
/* hmm, you want your own configuration, do you? */
if (getuid() == geteuid() && getgid() == getegid())
{
@@ -328,6 +371,11 @@
.text section. This is passed to ldpreload() below */
if (preload || callback)
{
+ if(nlibs==11)
+ {
+ fdprintf(2, "%s: too many preloads\n",argv[0]);
+ exit(EXIT_FATAL);
+ }
libs[nlibs] = alloca(strlen(buffer)+1);
strcpy(libs[nlibs], buffer);
nlibs++;
---- cut here ----

-----
KSR[T] Website : http://www.dec.net/ksrt
E-mail: ksrt@dec.net