Archive for the ‘solaris’ Category

This was a simple and nice vulnerability discovered by Larry W. Cashdollar as we can see in his email to the Bugtraq mailing list. The buggy code was part of /lib/svc/method/inetd-upgrade shell script and specifically in the below part.

# The Following blocks of code cause the inetconv generated services to be
# re-generated, so that the latest inetconv modifications are applied to all
# services generated by it.
inetdconf_entries_file=/tmp/iconf_entries.$$
# Create sed script that prints out inetd.conf src line from inetconv generated
# manifest.
cat <<EOF > /tmp/inetd-upgrade.$$.sed
/propval name='source_line'/{
n
s/'//g
p
}
/from the inetd.conf(4) format line/{
n
p
}
EOF
# get list of inetconv generated manifests
inetconv_manifests=`/usr/bin/find /lib/svc/manifest -type f -name \*.xml | \
/usr/bin/xargs /usr/bin/grep -l "Generated by inetconv"`
# For each inetconv generated manifest determine the instances that should
# be disabled when the new manifests are imported, and generate a file with
# the inetd.conf entries from all the manifests for consumption by inetconv.
...
# add the manifest's inetd.conf src line to file for inetconv
sed -n -f /tmp/inetd-upgrade.$$.sed $manifest >> \
$inetdconf_entries_file
done
rm /tmp/inetd-upgrade.$$.sed
# Check whether we've ever run inetconv before by looking for the
# configuration file hash. If we haven't run it before, then we need
# to enable services based on inetd.conf. If we have, then the
# repository is authoritative. `unimported' will be 0 if the hash exists.
svcprop -qp hash svc:/network/inetd:default
unimported=$?
# Run inetconv on generated file, overwriting previous manifests and values
# in repository.
/usr/sbin/inetconv -f -i $inetdconf_entries_file

As you can see, it generates a temporary file using the parent process’ PID (by utilizing the “$$” BASH internal environment variable). Then, generates the appropriate inetconv manifest, deletes the temporary file and runs it. The problem is that the temporary file name can be easily guessed and it is stored in /tmp where an unprivileged user has write access by default.
Larry W. Cashdollar provided a PoC Perl script to demonstrate the exploitation of this vulnerability.

First it gets a processing listing and if it finds one that includes “inetd-upgrade” string it will create a symbolic link at “/tmp/iconf_entries.” where PID is the PID of the “inetd-upgrade” process that points to “/etc/passwd” file. Due to this vulnerability, this script will modify the contents of /etc/passwd with the newly generated inetd.conf entries.

This was fixed with patch 137097-01 for SPARC and 137098-01 for X86 architectures respectively. The bug report of this issue was the 6392582 with synopsis “Upgrade from S10 FCS to snv_34 wrongly enables CDE RPC services”.

This vulnerability affects both Solaris 10 (on x86 as well as SPARC) and OpenSolaris. Since there is no reference on credits for this vulnerability in Oracle’s site I’ll assume that SecurityFocus is correct and credit prdelka as the person who discovered and disclosed this bug. In his released notes (sun-su-bug.txt) he discusses everything you need to know for this vulnerability. In any case, I’ll write about it too. :P

So, the buggy code can be found at usr/src/cmd/su/su.c and specifically in the main() function of su(1) utility. Here is the buggy code now…

You can see from the comment what this code does. To do this, it loops through each environment variable defined in the ‘initenv[]’ array which is defined above. Inside this ‘for’ loop you can read this code:

It retrives each environment variable using getenv(3) and skips variables beginning with ‘/’ for security purposes. Next, if it finds the “TZ” environment variable (using strcmp(3)) it will copy the appropriate time-zone to it. Otherwise, if this isn’t the “TZ” variable it will execute the ‘else’ code which is shown below.

Here, ‘var’ is initialized with the pointer returned by malloc(3). This code is used to allocate heap space for:

strlen(initenv[j]) + strlen(initvar) + 2

And then store the value of the environment variable (which is the ‘initvar’) to the environment variable ‘initenv[]’. The additional two Bytes are for the NULL termination and “=” character that is used for the assignment.
The bug here is that there is no check on the return value of malloc(3). Consequently, if user could make malloc(3) fail it will result in having a NULL return value as we can read at its man page. Because of this, ‘var’ can be NULL and the subsequent strcpy(3) and strcat(3) copy operations to it will result in a NULL pointer dereference.
prdelka coded a PoC code to demonstrate the vulnerability.

/* Sun Solaris <= 10 'su' NULL pointer exploit
===========================================
because these are so 2009 now. I would exploit
this but my name is not spender or raptor. Sun
do not check a call to malloc() when handling
environment variables in 'su' code. They also
don't check passwords when using telnet so who
cares? You have to enter your local user pass
to see this bug. Enjoy!
admin@sundevil:~/suid$ ./x
[ SunOS 5.11 'su' null ptr PoC
Password:
Segmentation Fault
-- prdelka
*/

Initially, he allocates space for the resource limit structure using malloc(3). Then, using getrlimit(2) system call he obtains the maximum size of data segment of his process. He allocates a 300KB buffer, initialize it with “A” and NULL terminates it. The first bytes of the allocated buffer are set to “LC_ALL=” and the current resource limit is set to 16.4KB using setrlimit(2). Finally, a new process is spawned that will execute ‘/usr/bin/su’ passing to it the huge “LC_ALL” environment variable. When su(1) will attempt to allocate sufficient space for the “LC_ALL”‘s value it will force malloc(3) to fail since it has a resource limit of 16.4KB while it attempts to allocate 300KB. This will trigger the strcpy(3) and strcat(3) write operations and of course, the NULL pointer dereference.

Exploitation of these vulnerabilities results in the execution
of arbitrary code with the privileges of the nobody user.
In addition, the attacker has access to the raw socket used by
the snoop program. This allows them to capture any traffic visible
to the network interface used.

The bugs are really easy to spot by having a quick look at src/cmd/cmd-inet/usr.sbin/snoop/snoop_smb.c which is the SMB parsing code for snoop(1M) utility. Here is an example of such buffer overflow…

Now if we move back to interpret_negprot() we’ll see that if this SMB packet is not a server response, it will fall to the ‘else’ clause which will initially use get2() to parse this as a request (since it’s not a response) SMB packet. The comment let us know that a request is composed of two members, a signed short that contains the Bytes count and a structure that has a format and a name.
The code continues by incrementing ‘protodata’ to reach the structure and then it enters a ‘while’ loop for the specified bytes if ‘F_SUM’ (which stands for ‘display summary line’) flag is specified. If this is the case, it will attempt to copy the user controlled SMB name to the stack allocated ‘dialect’ buffer which has size of 256 bytes using sprintf(3)!
Obviously, a user can send a request with a name larger than 256 bytes and thus overflow the ‘dialect’ buffer and execute arbitrary code in the context of snoop(1M).
Of course, this was patched by replacing the sprintf(3) call with snprintf(3) that performs bound checks given a correct size limit. Here is the patch for the above bug…

So, the patch is quite simple but you’ll probably wonder where are the “multiple” memory corruption vulnerabilities, ok… Let’s start..
In interpret_trans() there is another buffer overflow in case of a request with “display detail lines” flag enabled (this is the F_DTAIL one) that doesn’t use Unicode. Here is this one:

So, are there more of these? Yes! interpret_tconX() has four more sprintf(3) overflows, interpret_sesssetupX() has eight and even interpret_default() has one while parsing the ‘s’ or ‘S’ values!
There is a public exploit (hoagie_snoop.c) for this by andi of VOID.AT Security which is available here. Let’s have a quick look at it…

From this we can easily deduce that the exploit will use either of these structures depending on the target machine. Unfortunately, on a system with randomization this method won’t work since system(3)’s address could change. In any case, let’s move on to the usage() function…

After clearing the buffer by zeroing it out, it initializes the TCP header using the information passed to it through its arguments, sets ‘data’ to point to the ‘packet’ after the TCP header and initializes ‘length’ to 4 since this is the location where the size of the SMB packet’s data should be, it will be filled at the end. Next, the code goes like this:

The “\xffSMB” is used as an identifier that must start with 0xff and contain the ASCII characters SMB, the length is also incremented by four to move to the next segment of the packet. The next part of the packet will contain whatever SMB command code you want and it continues like this:

As we saw in the SMB header structure, the packet should now contain: error class (1 Byte), result value (1 Byte) and error code (2 Bytes). Since those are useless for our purpose, they’re all filled with ‘SMB_HEADER_FILLER’ which is 0x20. The next part of the header is 1 Byte for flags and another two flags 2…

The first one is filled with 0x18 (which will return true when masked with either F_DTAIL that is 0x8 as well as F_TIME) and the flags2 with 0x80 which matches with F_DROPS (display drops) and 0x01 which is F_NOW (display in realtime). The next members of the SMB header are:
– 12 Bytes for re[] array

The last assignment is for the ‘format’ member of the ‘dialect’ structure and after this point, the malicious string will be copied in the ‘name[]’ array’s position which is the exact next one like this:

The content which is the string that will overflow ‘dialect’ buffer is copied in the packet and NULL termination takes place. Now, the author updates the length bytes that were omitted during the first steps that should normally contain the total size of the data section like this:

Here he creates a raw socket and initializes its socket structure with the appropriate data, also if no command is set by the user the author uses a “uname -a” as the default which is redirected to a file in /tmp/ directory. Next…

Here the author places the hardcoded address of system(3) as the return address. By doing so, system(3) will attempt to execute whatever it finds in the stack before it. In our case this is a bunch of space ASCII characters (0x20 in hex.) followed by a ‘;’ character and the command you selected. The rest of the code is probably what you’ve been expecting…

This vulnerability was disclosed by Sun Microsystems on 23 November 2009 and it affects Solaris 10 as well as OpenSolaris based upon builds snv_99 through snv_123. The buggy code resides in usr/src/cmd/ssh/sshd/sshd.c source code file. So, as you can see below…

/*
* Main program for the daemon.
*/
int
main(int ac, char **av)
{
extern char *optarg;
...
/*
* We don\'t want to listen forever unless the other side
* successfully authenticates itself. So we set up an alarm which is
* cleared after successful authentication. A limit of zero
* indicates no limit. Note that we don\'t set the alarm in debugging
* mode; it is just annoying to have the server exit just when you
* are about to discover the bug.
*/
(void) signal(SIGALRM, grace_alarm_handler);
if (!debug_flag)
(void) alarm(options.login_grace_time);
...
* The child is about to start the first key exchange while the monitor
* stays in altprivsep_start_and_do_monitor() function.
...
/*
* Start the monitor. That way both processes will have their own
* PKCS#11 sessions. See the PKCS#11 standard for more information on
* fork safety and packet.c for information about forking with the
* engine.
*/
altprivsep_start_and_do_monitor(options.use_openssl_engine,
inetd_flag, newsock, startup_pipe);
...
authenticated:
/* Authentication complete */
(void) alarm(0);
/* we no longer need an alarm handler */
(void) signal(SIGALRM, SIG_DFL);
if (startup_pipe != -1) {
(void) close(startup_pipe);
startup_pipe = -1;
}
/* ALTPRIVSEP Child */

This is part of the main code of the SSH daemon, in the above snippet you can read that it initializes an alarm signal handler with grace_alarm_handler() routine which is basically just this:

However, this signal handler affects the unprivileged child but the monitor that is being later initialized also uses an event on that communication pipe. This could result in a DoS situation because of the dangling child thread in case of a time-out and consequently, exit because of the monitor.
To fix this, the patch updated various routines, first of all the above signal handler was updated to include more useful comments as you can read below.

/*
-* Signal handler for the alarm after the login grace period has expired.
+* Signal handler for the alarm after the login grace period has expired. This
+* is for the (soon-to-be) unprivileged child only. The monitor gets an event on
+* the communication pipe and exits as well.
*/
static void
grace_alarm_handler(int sig)
{
- /* XXX no idea how fix this signal handler */
/* Log error and exit. */
- fatal("Timeout before authentication for %s", get_remote_ipaddr());
+ fatal("Timeout before authentication for %.200s", get_remote_ipaddr());
}

Next, the initialization of the alarm handler was moved a few lines after in order to be able to handle monitor too like this:

- /*
- * We don\'t want to listen forever unless the other side
- * successfully authenticates itself. So we set up an alarm which is
- * cleared after successful authentication. A limit of zero
- * indicates no limit. Note that we don\'t set the alarm in debugging
- * mode; it is just annoying to have the server exit just when you
- * are about to discover the bug.
- */
- (void) signal(SIGALRM, grace_alarm_handler);
- if (!debug_flag)
- (void) alarm(options.login_grace_time);

Which was added here:

packet_set_nonblocking();
/*
* Start the monitor. That way both processes will have their own
* PKCS#11 sessions. See the PKCS#11 standard for more information on
* fork safety and packet.c for information about forking with the
* engine.
+ *
+ * Note that the monitor stays in the function while the child is the
+ * only one that returns.
*/
altprivsep_start_and_do_monitor(options.use_openssl_engine,
inetd_flag, newsock, startup_pipe);
/*
+ * We don't want to listen forever unless the other side successfully
+ * authenticates itself. So we set up an alarm which is cleared after
+ * successful authentication. A limit of zero indicates no limit. Note
+ * that we don't set the alarm in debugging mode; it is just annoying to
+ * have the server exit just when you are about to discover the bug.
+ */
+ (void) signal(SIGALRM, grace_alarm_handler);
+ if (!debug_flag)
+ (void) alarm(options.login_grace_time);
+
+ /*
* The child is about to start the first key exchange while the monitor
* stays in altprivsep_start_and_do_monitor() function.
*/
(void) pkcs11_engine_load(options.use_openssl_engine);

Also, the ‘authenticated’ label was removed but this is part of bug ID 6875551 which non-security related.

In addition to this, src/cmd/ssh/libssh/common/packet.c was also changed because of this patch, first to include the following comments…

DBG(debug("packet_send done"));
}
/*
* Waits until a packet has been received, and returns its type. Note that
* no other data is processed until this returns, so this function should not
* be used during the interactive session.
+*
+* The function is also used in the monitor to read the authentication context
+* in aps_read_auth_context() via packet_read_seqnr(), before the monitor enters
+* aps_monitor_loop() and starts using the process_input() function.
*/
int
packet_read_seqnr(u_int32_t *seqnr_p)
{

and secondly, to update packet_read_seqnr() function to perform more reliable handling of a possible closed or errorneous read operation on the connection’s socket.

Instead of the simple log() and fatal() calls, the daemon will now check the connection using packet_connection_is_on_socket() and only if this returns true, it’ll print the log() error message that the connection was closed, otherwise it’ll print a debugging message using debug() and move the execution flow to fatal_cleanup().
In a similar manner, in case of a read() that returns a negative value it will check the return value of packet_connection_is_on_socket() and it’ll either invoke fatal() because it failed to read from the socket or because of failure in reading from the communication pipe. For better understanding of this patch you’ll need to know exactly what packet_connection_is_on_socket() does. So, this is a function located at src/cmd/ssh/libssh/common/packet.c that returns true if the remote host is connected via socket or false in any other case.
Back to the patch we should now move to src/cmd/ssh/sshd/altprivsep.c which as it is implied by its name is the source code file that includes part of the privilege separation of SSH protocol. First of all, altprivsep_start_and_do_monitor() was changed to include some comments as you can read here:

I just saw this “new” vulnerability report. The bug was discovered and fixed by Sun Microsystems and it affects OpenSolaris based upon builds snv_106 through snv_124 on both SPARC and x86 platforms. Here is the vulnerable code path is through tcp_do_getsockname() which resides in common/inet/tcp/tcp.c.

This is the code that handles the ‘getsockname’ operation on TCP sockets. In case of an IPv6 socket it will initially check that its size is valid and then proceed with checking the state of the TCP connection. If this is less than ‘TCPS_BOUND’ which as we can read at common/inet/tcp.h is:

It will fall to the ‘else’ clause where it’ll attempt to initialize ‘sin6->sin6_addr’ with ‘tcp->tcp_ip6h->ip6_src’ which is the IPv6 source address of the TCP header. However, as James Carlson pointed out, the ‘tcp->tcp_ip6h’ pointer could be NULL and thus, this operation will result in NULL pointer dereference during that ‘else’ part.
A similar vulnerability was also present in the tcp_do_getpeername() routine which is located in the same source code file. For completeness, here is the definition of ‘tcp_ip6h’ pointer from ‘tcp_t’ data type as seen in common/inet/tcp.h header file.

/*
* Control structure for each open TCP stream,
* defined only within the kernel or for a kmem user.
* NOTE: tcp_reinit_values MUST have a line for each field in this structure!
*/
#if (defined(_KERNEL) || defined(_KMEMUSER))
typedef struct tcp_s {
...
ip6_t *tcp_ip6h; /* IPv6 header in the buffer */
...
#ifdef DEBUG
pc_t tcmp_stk[15];
#endif
} tcp_t;

This vulnerability was released by Sun Microsystems and it affects OpenSolaris based upon builds snv_106 through snv_126 for both x86 and SPARC architectures. The buggy code can be found at usr/src/uts/common/inet/tcp/tcp.c. Here is the exact code…

This is a well known function, as you can see after initializing the ‘connp’ pointer with the ‘proto_handle’ pointer, it performs some basic assertions. These are that: there are some initialized credentials for that socket, the connection’s reference counter is at least two and the ‘connp->conn_upper_handle’ which contains the upper handle for sockfs transport layer is not NULL.
Following, function tcp_sendmsg() will immediately return EOPNOTSUPP (aka Operation not supported on socket) in case of a ‘msg->msg_controllen’ not equal to zero. This variable is a ‘socklen_t’ counter that is used to contain the length of ancillary data buffer. However, as you can easily deduce, the ‘mp’ pointer which contains the message block descriptor as we can read from usr/src/stand/lib/sock/socket_impl.h will never be freed.

So, in the above code snippet you can see that ‘ut’ pointer is initiallized with the size of UTMPX file’s size divided by the size of structure ‘futmpx’. The size is calculated to fit enough ‘utmpx’ pointers for each record of UTMP log file.
After allocating the required space using malloc(3), a call to utmpxname() takes place to change the filename. Then, variable ‘utmpbegin’ is initialized with the previously allocated ‘ut’ pointer and ‘utmpend’ with ‘utmpbegin + size’ which will return a pointer to the end of the ‘ut’ structure. The subsequent call to setutxent() will reset the input stream to the beginning of the file.
Now, the code execution moves to the main copying loop which is a simple while loop that will initialize ‘utp’ with getutxent() which returns the next entry of the UTMPX file. It will then copy the returned ‘utmpx’ structure to ‘ut’ and increment the pointer to continue the copying. The problem here is that ‘ut’ was initialized based on the ‘sbuf.st_size’ value retrieved using stat(2) system call. Between the stat(2) of the file and the actual copy using memcpy(3) there might have been new entries in the log file that will result in heap memory corruption since the only check is that getutxent() has not reached the last entry in the UTMPX file.
This was fixed by applying the following patch:

The new loop checks also that the ‘ut’ pointer has not reached the end of the allocated space which is stored in ‘utmpend’.
Apart from this nice bug, Sun Microsystems also silently fixed another one similar heap memory corruption vulnerability in whodo (located in src/cmd/whodo/whodo.c). Here is the patch of whodo: