oss-sec mailing list archives

Re: Tunnel Blick: Multiple Vulnerabilities to Local Root and DoS (OS X)


From: Kurt Seifried <kseifried () redhat com>
Date: Tue, 14 Aug 2012 00:03:18 -0600

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 08/13/2012 06:34 AM, Jason A. Donenfeld wrote:
Hi Kurt,

Sure, I'll trace each one, and include line numbers with the code. 
This code comes from: 
http://code.google.com/p/tunnelblick/source/browse/trunk/tunnelblick/openvpnstart.m?r=2095

Thanks,

without the details this would have been impossible, as it is
I'm going to break one CVE editorial rule, but I think it's for a good
reason (but I could also be wrong). So in summary:

==================
1. A race condition in file permissions checking can lead to local
root. - TOCTOU

Please use CVE-2012-3483 for this issue

==================
2. Insufficient checking of merely 0:0 744 can lead to local root on
systems with particular configurations.

Please use CVE-2012-3484 for this issue

==================
3. Insufficient validation of path names can allow for arbitrary
kernel module loading, which can lead to local root.
4. Insufficient validation of path names can allow execution of
arbitrary scripts as root, leading to local root.
5. Insufficient path validation in errorExitIfAttackViaString can lead
to deletion of files as root, leading to DoS.

Please use CVE-2012-3485 for these issues

==================
6. Allowing OpenVPN to run with user given configurations can lead to
local root.

Please use CVE-2012-3486 for this issue

==================
7. Race condition in process killing. - TOCTOU

Please use CVE-2012-3487 for this issue



although #1 and #7 are both TOCTOU race conditions (and thus should be
merged) one is a classic file perms check/use, the other is quite
different, process listing/killing, so I split the CVE (they are
sufficiently different to warrant it I think).



1. A race condition in file permissions checking can lead to local
root. PoC:
http://git.zx2c4.com/Pwnnel-Blicker/tree/pwnnel-blicker.c

927   int runScript(NSString * scriptName, 928                       int
argc, 929                    char     * cfgName, 930
char     * cfgLoc) 931        { * 964                 if (
checkOwnerAndPermissions(scriptPath, 0, 0, @"744")  ) { 965 966
fprintf(stderr, "'%s' executing...\n", [scriptName UTF8String]); *
967                       returnValue = runAsRoot(scriptPath,
[NSArray array]); 968                     fprintf(stderr, "'%s'
returned with status %d\n", [scriptName UTF8String], returnValue); 
969                   }

Here, there's a race condition between the two stared lines.


2. Insufficient checking of merely 0:0 744 can lead to local root
on systems with particular configurations.

964                   if (  checkOwnerAndPermissions(scriptPath, 0, 
0, @"744")  ) { and 801                   if (  !
checkOwnerAndPermissions(preConnectPath, 0, 0, @"744")  ) { and 847
if (  ! checkOwnerAndPermissions(postTunTapPath, 0, 0, @"744")  )
{ and 1675                    if (  !
checkOwnerAndPermissions(filePath, 0, 0, @"744")  ) {       //
shell scripts are 744

Testing a file for whether or not it's 744 and owned by root:root
is not sufficient for deciding whether a unprivileged should be
able to run it as root. This not only makes every 744 root:root
file on the file system a potential vector, but destroys the nosuid
mount flag OS X uses for all user mountable images and network
shares.


3. Insufficient validation of path names can allow for arbitrary 
kernel module loading, which can lead to local root.

86        execPath = [[NSString stringWithUTF8String:argv[0]] 
stringByDeletingLastPathComponent]; 1355      void loadKexts(unsigned
int bitMask) 1356     { 1357      if (  ( bitMask &
(OPENVPNSTART_OUR_TAP_KEXT | OPENVPNSTART_OUR_TUN_KEXT) ) == 0  )
{ 1358                return; 1359        } 1360 1361     NSMutableArray*
arguments = [NSMutableArray arrayWithCapacity: 2]; 1362           if (
(bitMask & OPENVPNSTART_OUR_TAP_KEXT) != 0  ) { 1363
NSString * tapkext = [@"tap" stringByAppendingString: 
TunTapSuffixToUse([execPath stringByAppendingPathComponent:
@"tap"])]; 1364               [arguments addObject: [execPath 
stringByAppendingPathComponent: tapkext]]; 1365
fprintf(stderr, "Loading %s\n", [tapkext UTF8String]); 1366       } 
1367      if (  (bitMask & OPENVPNSTART_OUR_TUN_KEXT) != 0  ) { 1368
NSString * tunkext = [@"tun" stringByAppendingString: 
TunTapSuffixToUse([execPath stringByAppendingPathComponent:
@"tun"])]; 1369               [arguments addObject: [execPath 
stringByAppendingPathComponent: tunkext]]; 1370
fprintf(stderr, "Loading %s\n", [tunkext UTF8String]); 1371       } 
1372 1373         becomeRoot();

1374      int status; 1375        int i; 1376     for (i=0; i < 5; i++)
{ 1377                NSTask * task = [[[NSTask alloc] init]
autorelease]; 1378 1379               [task
setLaunchPath:@"/sbin/kextload"]; 1380 1381           [task
setArguments:arguments]; 1382 1383            [task launch];

As you can see, the file name of the kernel extension being loaded
is derived from argv[0], which can be trivially bypassed (
execl(..., "/attacker/controlled/argv/zero", ...) ).


4. Insufficient validation of path names can allow execution of 
arbitrary scripts as root, leading to local root. PoC:
http://git.zx2c4.com/Pwnnel-Blicker/tree/pwnnel-blicker-for-kids.sh

 86       execPath = [[NSString stringWithUTF8String:argv[0]] 
stringByDeletingLastPathComponent]; 919       void
runOpenVpnToGetVersion(NSString * openvpnVersion) 920 { 921
NSString * openvpnPath = openvpnToUsePath([execPath 
stringByAppendingPathComponent: @"openvpn"], openvpnVersion); 922
runAsRoot(openvpnPath, [NSArray arrayWithObject: @"--version"]); 
923   }

This basically amounts to this being run as root:  $(dirname 
argv[0])/openvpn --version.



5. Insufficient path validation in errorExitIfAttackViaString can
lead to deletion of files as root, leading to DoS. 164                }
else if( strcmp(command, "deleteLogs") == 0 ) { 165                           if (argc ==
4) { 166                                      NSString* configFile = [NSString
stringWithUTF8String:argv[2]]; 167
errorExitIfAttackViaString(configFile); 168
unsigned cfgLocCode = atoi(argv[3]); 169
deleteLogFiles(configFile, cfgLocCode); 170
syntaxError = FALSE; 171                  } 1735      void
errorExitIfAttackViaString(NSString * string) 1736    { 1737      BOOL
startsWithDot = [string hasPrefix: @"."]; 1738            NSRange r =
[string rangeOfString: @"/.."]; 1739      if (   startsWithDot 1740
|| (r.length != 0)  ) { 1741          fprintf(stderr, "Tunnelblick
openvpnstart: Apparent attack detected; string being tested is
%s\n", [string UTF8String]); 1742             [pool drain]; 1743
exit(EXIT_FAILURE); 1744          } 1745      }

The "exit if attack string" function doesn't check for links of
any kind, symbolic or hard, so this validation is not sufficient.


6. Allowing OpenVPN to run with user given configurations can lead
to local root. 939            if (  [configFile hasSuffix: @"tblk"]  )
{ 940             unsigned  cfgLocCode = atoi(cfgLoc); 941
switch (cfgLocCode) { 942                     case 0: 943
configPrefix = [NSHomeDirectory() 
stringByAppendingPathComponent:@"/Library/Application 
Support/Tunnelblick/Configurations"]; 944
break; 945                    case 1: 946
configPrefix = [NSString stringWithFormat:@"/Library/Application
Support/Tunnelblick/Users/%@", NSUserName()]; 947
break; 948                    case 2: 949
configPrefix = [execPath stringByAppendingPathComponent:
@"Deploy"]; 950                           break; 951                  case
3: 952                            configPrefix = [NSString
stringWithString: @"/Library/Application
Support/Tunnelblick/Shared"]; 953                         break; 954
default: 955                      break; 956              } 957
} The purpose of this SUID helper is so that users can run OpenVPN
with their own provided configuration files as root. OpenVPN
configuration files can run scripts based on various OpenVPN
events.


7. Race condition in process killing. 1496    BOOL isOpenvpn(pid_t
pid) 1497     { 1498          BOOL                            is_openvpn      = FALSE; 1499           int             
                        count
= 0, 1500             i                       = 0; 1501               struct kinfo_proc*      info            = NULL; 
1502 
1503          getProcesses(&info, &count); 1504           for (i = 0; i < count;
i++) { 1505           char* process_name = info[i].kp_proc.p_comm; 
1506          pid_t thisPid = info[i].kp_proc.p_pid; 1507             if
(pid == thisPid) { 1508                               if (strcmp(process_name, "openvpn")==0)
{ 1509                                        is_openvpn = TRUE; 1510                         } else { 1511           
                        is_openvpn
= FALSE; 1512                         } 1513                          break; 1514                     } 1515      } 
1516
free(info); 1517              return is_openvpn; 1518 }

991           if(isOpenvpn(pid)) { 992                        becomeRoot(); 993                       didnotKill =
kill(pid, SIGTERM); and 1022                  if(strcmp(process_name, "openvpn")
== 0) { 1023                          becomeRoot(); 1024                              didnotKill = kill(pid,
SIGTERM);

There's a race between checking the name of the process and
killing that PID. Since PIDs are cycled, eventually that PID could
point to a different process the user shouldn't have permission to
kill.


Hope this clarifies things. Let me know if you have more
questions.

Jason



- -- 
Kurt Seifried Red Hat Security Response Team (SRT)
PGP: 0x5E267993 A90B F995 7350 148F 66BF 7554 160D 4553 5E26 7993

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.12 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iQIcBAEBAgAGBQJQKeomAAoJEBYNRVNeJnmTLMwQALPTVxjFKEb4qtnsB5h0GGV+
LkgubkWO5RuaedLvQsWNqqxDDgDWRDMP+PRwicTLq1bqHJ4ifa54eJnm8q/yyinX
tXCsQrDE0O6f5jXrdEsoOwdeWRn82zD4diFAWEeOI7U166TRTYyFIAmIa0MDgZaw
PHNsS8RccDXOc/Rraz3iV/7sWzU6e6KpmW1vhpbXjxUz6zuSGHATTdPPFAC/sTwf
HycD1wMP1FmSbeBfoliLpb+Ibj+AZUCqsn4efwr0LgsZCmoKvpnEsbfRaNEaeARo
1LzGhCBFWgCSePmebmqWsYP9oPfWlRCwRWrCzX7j/Y272MytkYXbLeUfVQvPc0TW
okyfwtHvNpSHU5AqSMt2DmE62U/275feJgfRTmP4xDGOFE9UTiVKjFz2Jy//KRnY
KvLvoIC7SqPIQvy9bb8QE8IqMyKGq/oF1SP28iOsCSFDes+CRwkr6xXSILiDCzd9
5J4qNi0vSdp1EabQEHtOqwE9kITbO/pSCswd9KQDp8KHV5GeLV76XpBNVY1vDjH6
WWDC0715JQa+TrArCeOIJBI684sdvPPQcWB+P45j/OC4DKHOn4koRa0+LYingq5a
ntpw+fkdhhaf+3StaBlROOEHHkbhdxg8gZgGrRH8r5pQ0A8+bZXuZsJC1ezpp4BM
zN+hCjbnDxxU+5/2jZUr
=amtN
-----END PGP SIGNATURE-----


Current thread: