About | Buy Stuff | Industry Watch | Learning Curve | Products | Search | Twitter
Home » Industry Watch » Coldspots

.SoftwareUpdateAtLogout

'I didn't have to enter my password to update. Is this typical for everyone?'


Buy It

Try It

The Safari 4.0.5 update: Leopard users had to submit their password, Snow Leopard users didn't. There's a 'technology' under the bonnet with beginnings in 10.4 Tiger for this. An earlier article looked briefly at the mystery.

The Safari update - whether targeting 10.5 Leopard or 10.6 Snow Leopard - needs root privileges. The catch is that 10.5 users are required to submit their admin passwords (or the equivalent) whilst 10.6 users are not - they somehow get root escalation for free.

Code run from user land that can trigger (fool) a system into performing tasks as root represents a root exploit of the system. Both the Opener hole and the systemloginitems hole were root exploits. They were open for a long time; they seem to have finally been closed. But there could be another one.

The discovery comes courtesy of an 'innocent' comment by 'zapblast' at the MacRumors forums. It was also noted by others that Leopard 10.5 required authentication (submitting a password) to do the same thing.

The system runs at 'SUM (single user mode) root' before and after login. There is no 'user' per se in either case. There is not even root. There is only SUM (single user mode) root which coincidentally has a higher privilege level than ordinary root itself.

Something is triggering a change in the otherwise protected logout procedure. Software Update.app and its dependencies contain some of the clues.

/System/Library/CoreServices/Software Update.app
/System/Library/PrivateFrameworks/SoftwareUpdate.framework


Running Xstrings on the Software Update binary yields the following.

000000000001423e prepareForLogoutAndInstall
000000000001425d adminAuthorizationForInstall:
000000000001489c _willProductsRequireLogout:
0000000000014b9a runPrivilegedHelperTool
0000000000014bb2 preauthorizeForPrivilegedHelperTool
0000000000014bd6 isCurrentUserAdministrator
0000000000014bf1 preauthorizeForInstallationOfProducts:

The code is prepared to prompt for a password. Software Update.app for 10.5 namely had a 'suapphelper' embedded.

0000000000001f84 admin
0000000000001f8c /var/db/.SoftwareUpdateAtLogout
0000000000001fac /Library/Updates
0000000000004160 com.apple.suapphelper
0000000000006f84 admin
0000000000006f8c /var/db/.SoftwareUpdateAtLogout
0000000000006fac /Library/Updates
0000000000008180 com.apple.suapphelper
000000000000822a com.apple.translate

Running Xstrings on the Software Update framework binary yields the following.

0000000000026eca prepareForLogoutAndInstall
0000000000026eec c24@0:8^{AuthorizationOpaqueRef=}16
0000000000026f12 SUHelperProxy
0000000000026f20 adminAuthorizationForInstall:
0000000000026f40 bytes
0000000000026f4c isCurrentUserAdministrator
0000000000026f6c autorelease
0000000000026f78 length
0000000000026f7f com.apple.suhelperd
0000000000026f98 /Library/Updates
0000000000026fb0 /var/db/.SoftwareUpdateAtLogout
0000000000026fd0 index.trust
0000000000026fdc root
0000000000026fec index.plist
0000000000026ff8 Unable to remove old index.trust
0000000000027019 Could not write index.trust
0000000000027038 Failed permission set on index.trust
0000000000027060 Unknown file type encountered removing installed update.
00000000000270a0 Failed to remove directory at path %s

Note the entry at 0000000000026fb0: the system puts and then looks for what may be an empty file at the path /var/db/.SoftwareUpdateAtLogout.

Note as well that the path /var/db is root-owned and protected and remember that Snow Leopard users are not required to authenticate.

The path /Library/Updates is also mentioned. See the entry at 0000000000026fec - the 'index.plist' for Safari 4.0.5 is reproduced below. This file specifies which updates are to be installed. (The updates are found in the same hive.)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>InstallAtLogout</key>
    <array>
        <string>061-7784</string>
    </array>
    <key>ProductPaths</key>
    <dict>
        <key>061-7784</key>
        <string>061-7784</string>
    </dict>
</dict>
</plist>

There are also repeated references to 'index.trust'.

suhelperd, SUHelperProxy

suhelperd may also be a key.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
   <key>Label</key>
   <string>com.apple.suhelperd</string>
   <key>MachServices</key>
   <dict>
      <key>com.apple.suhelperd</key>
      <true/>
   </dict>
   <key>OnDemand</key>
   <true/>
   <key>ProgramArguments</key>
   <array>
      <string>/System/Library/PrivateFrameworks/SoftwareUpdate.framework/Resources/suhelperd</string>
   </array>
</dict>
</plist>

But suhelperd is only 'owned' by root - it doesn't have a set ID bit set. It can only run as root if a root process starts it. The first launchd process (a root process) starts suhelperd when Software Update.app checks for updates - and runs it as root. Some form of IPC would need to be used - how can launchd/suhelperd know the request's genuine? By checking bundle IDs and root certificates? Signed code can be subverted and the user isn't in control.

The SUHelperProxy class might also be a clue.

0000000000051028 -[SUHelperProxy isAuthorized]
0000000000051046 -[SUHelperProxy _createAdminAuthorizationForDaemon]
000000000005107a -[SUHelperProxy init]
0000000000051090 -[SUHelperProxy dealloc]
00000000000510a9 -[SUHelperProxy authorizeTool:]
00000000000510c9 -[SUHelperProxy prepareForLogoutAndInstall]
00000000000510f5 -[SUHelperProxy moveSharedFilesFromTempPath:forProductKey:]
0000000000051131 -[SUHelperProxy makeQueues]
000000000005114d -[SUHelperProxy removeMetadataCacheFromUpdates]
000000000005117d -[SUHelperProxy moveMetadataCacheToUpdatesFromPath:]
00000000000511b2 -[SUHelperProxy removeIndexFromUpdates]
00000000000511da -[SUHelperProxy moveIndexToUpdatesFromPath:]
0000000000051207 -[SUHelperProxy createDirectoryForProductKey:]
0000000000051236 -[SUHelperProxy removeDistForProductKey:withFilename:]
000000000005126d -[SUHelperProxy removeProductDirectoryForKey:]
000000000005129c -[SUHelperProxy sessionLock]
00000000000512b9 -[SUHelperProxy acquireSessionLock:]
00000000000512de -[SUHelperProxy releaseSessionLock:]
0000000000051303 -[SUHelperProxy cancelSessionLockBelowPriority:]
0000000000051334 -[SUHelperProxy createSpecialModeCookie:]
000000000005135e -[SUHelperProxy removeSpecialModeCookie]
0000000000051387 -[SUHelperProxy _launchDaemonMode]
00000000000513aa +[SUHelperProxy sharedProxy]
00000000000513c7 -[SUHelper run]
00000000000513d7 -[SUHelper _isClientPort:]
00000000000513f2 -[SUHelper sessionLock]
000000000005140a -[SUHelper(PrivateHelperMethods) _trustFilePath]
000000000005143b -[SUHelper(PrivateHelperMethods) _moveFileAndSetSanePermissionsFrom:to:]
0000000000051484 -[SUHelper(PrivateHelperMethods) _sharedPathForProductKey:createIfMissing:]
00000000000514d0 -[SUHelper(PrivateHelperMethods) _isSaneProductKey:]
0000000000051505 -[SUHelper removeSpecialModeCookie]
0000000000051529 -[SUHelper removeDistForProductKey:withFilename:]
000000000005155b -[SUHelper createDirectoryForProductKey:]
0000000000051585 -[SUHelper moveIndexToUpdatesFromPath:]
00000000000515ad -[SUHelper removeIndexFromUpdates]
00000000000515d0 -[SUHelper moveMetadataCacheToUpdatesFromPath:]
0000000000051600 -[SUHelper removeMetadataCacheFromUpdates]
000000000005162b -[SUHelper makeQueues]
0000000000051642 -[SUHelper moveSharedFilesFromTempPath:forProductKey:]
0000000000051679 -[SUHelper init]
000000000005168a -[SUHelper dealloc]
000000000005169e -[SUHelper createSpecialModeCookie:]
00000000000516c3 -[SUHelper removeProductDirectoryForKey:]
00000000000516ed -[SUHelper _areRightsValid:]
000000000005170a -[SUHelper _vendMachPortForPID:]
000000000005172b -[SUHelper _removeDeadPort:]
0000000000051748 -[SUHelper _shadowAuthorization:]
000000000005176a -[SUHelper authorizeTool:]
0000000000051785 -[SUHelper prepareForLogoutAndInstall]
00000000000517ac -[SUHelper acquireSessionLock:]
00000000000517cc -[SUHelper releaseSessionLock:]
00000000000517ec -[SUHelper cancelSessionLockBelowPriority:]

10.4 Tiger, 10.5 Leopard

Some of the above code is found in 10.5 Leopard where users are still asked to authenticate. Note the reference to the authorisation services ('system.privilege.admin') and the key argument 'preauthorize'.

0000000000022dac /var/db/.SoftwareUpdateAtLogout
0000000000022dcc /Library/Updates
0000000000022ddd caseInsensitiveCompare:
0000000000022df5 CaseInsensitive
0000000000022e08 c8@0:4
0000000000022e10 ^{AuthorizationOpaqueRef=}8@0:4
0000000000022e30 preauthorize
0000000000022e40 system.privilege.admin
0000000000022e58 @(#)PROGRAM:SoftwareUpdate  PROJECT:SoftwareUpdate-182

The code is not found in Tiger 10.4.

launchd-shutdown.log

The following was part of a test run to see if the system could be provoked into revealing more about this 'secret hook'.

sudo touch /var/db/.SoftwareUpdateAtLogout
sudo touch /var/db/index.trust

index.trust remained after reboot but .SoftwareUpdateAtLogout was gone. The 300 KB file 'launchd-shutdown.log' contained several mentions of com.apple.SoftwareUpdate and the helper daemon.

-73795  1 com.apple.launchd  5465 .com.apple.SoftwareUpdate   Stopping job.
-73770  1 com.apple.launchd  5465 .com.apple.SoftwareUpdate   Sent SIGTERM signal
-72525  1 com.apple.launchd  5465 .com.apple.SoftwareUpdate   Job was sent SIGTERM.
-71582  1 com.apple.launchd     0 com.apple.suhelperd         Job is inactive. Removing.
-71571  1 com.apple.launchd     0 com.apple.suhelperd         Closing receive right for com.apple.suhelperd
-71554  1 com.apple.launchd     0 com.apple.suhelperd         Mach service deleted: com.apple.suhelperd
  9053  1 com.apple.launchd  5465 .com.apple.SoftwareUpdate   Mach service deleted (port died): WakeUpProcessPort
  9147  1 com.apple.launchd  5465 .com.apple.SoftwareUpdate   Mach service deleted (port died): com.apple.axserver
  9181  1 com.apple.launchd  5465 .com.apple.SoftwareUpdate   Mach service deleted (port died): com.apple.tsm.portname
  9292  1 com.apple.launchd  5465 .com.apple.SoftwareUpdate   Dispatching kevent callback.
  9296  1 com.apple.launchd  5465 .com.apple.SoftwareUpdate   EVFILT_PROC event for job:
  9366  1 com.apple.launchd  5465 .com.apple.SoftwareUpdate   Reaping
  9416  1 com.apple.launchd  5465 .com.apple.SoftwareUpdate   Exited 0.082999 seconds after the first signal was sent
  9461  1 com.apple.launchd     0 .com.apple.SoftwareUpdate   Watching job (kickstart = false)
  9465  1 com.apple.launchd     0 .com.apple.SoftwareUpdate   Watching...
  9517  1 com.apple.launchd     0 .com.apple.SoftwareUpdate   Job is inactive. Removing.
  9553  1 com.apple.launchd     0 .com.apple.SoftwareUpdate   Removed

There are further references to 'root' in the file 'Distribution' in the Safari 4.0.5 download.

<choice id='su' suDisabledGroupID='Safari405SnowLeopard'>
    <pkg-ref id='com.apple.pkg.Safari405SnowLeopard' auth='Root' onConclusion='RequireRestart' >
            Safari4.0.5SnowLeopard.pkg
    </pkg-ref>
</choice>

<choice id='manual'>
    <pkg-ref id='manual' auth='Root' onConclusion='RequireRestart' >
            #Safari4.0.5SnowLeopard.pkg
    </pkg-ref>
</choice>

suhelperd has the key character strings as suspected.

0000000000007d80 /Library/Updates
0000000000007d98 /var/db/.SoftwareUpdateAtLogout
0000000000007db8 priority
0000000000007dc8 <%@ uid=%d, pid=%d, priority=%d
0000000000007de8 SUSessionLockAcquiredNotification
0000000000007e10 SUSessionLockReleasedNotification
0000000000007e38 SUSessionLockPreemptedNotification

There is no such module or the equivalent (suhelperd) on 10.4 or 10.5. This new twist in '.SoftwareUpdateAtLogout' 'technology' seems to have been implemented only for 10.6.

There are references to to /var/db in the launchd binary but they seem to be used in a different context.

The Bottom Line, The Weakest Link, The Closed Open Source

So how can Apple suddenly 'get root' on 10.6 without user authentication? A lot of people would really love to know.

Testing this peculiar scenario is time consuming. With enough time (and patience) it might be possible to get the system to reveal more of what is going on.

Things are absolutely great if all downloaded binaries and application packages are signed by Apple and/or 'index.trust' contains some sort of indication of authenticity.

But the fact remains that the scenario is set up a lot earlier by a process originating in 'user land' that's never escalated as was done explicitly on 10.5 and earlier.

And the only ostensible advantage to doing things this way is to not have to require a password.

With Apple's track record - the Opener and system login items security craters which were open for years and years - one would think they'd play it safe and just scrap the idea as folly and potentially bad PR, when all they win is not having to make the user submit a password.

Anyone (white hat or black) can know approximately how the system works: know where the flag file goes, know where the system will look for the actual update (which can be replaced by a rootkit). It could take a lot of time and a lot of patience to see if the system can be provoked into revealing more - entries in logs about encountering bogus logout commands etc.

But the file /var/db/.SoftwareUpdateAtLogout is created prior to logout and root privileges are needed to do anything in /var/db. But there's no privilege escalation for the software update, the Software Update binaries don't have a set user ID bit, and so forth.

Something in the system creates that file as root without user authentication. This means there's a 'trigger' in the system of some sort - some kind of IPC that's making this happen.

And that's where the weak spot would be.

The workings of Unix systems are well known. People know what file ownership means and they know how permissions and privilege escalation work. In knowing these things, they can trust their system.

Apple don't like documenting things. They like keeping parts of their 'open source' system closed. This is good for no one.

'I didn't have to enter my password to update. Is this typical for everyone?'
 - 'zapblast' at the MacRumors forums
'The media buzz over Opener went on the better part of a month and was then forgotten, but the fact remains that it is the single biggest security hole ever in the history of modern operating systems. No other operating system has ever offered such effortless escalation to superuser.'
 - Rixstep Industry Watch on Opener 3.9

See Also
Industry Watch: Opener 3.9
Learning Curve: Rooting 10.5.4
Developers Workshop: 061-7784
Industry Watch: Get Root on 10.5.4
Industry Watch: ARDAgent Here to Stay?
Coldspots: The Strange Case of Safari 4.0.5
Learning Curve: Of Sticky Bits & Preferences
Learning Curve: ARDAgent on Snow Leopard
Hotspots: SLIPOC — Root Exploit of Mac OS X
The Technological: Walking into an Apple Store
Red Hat Diaries: Number One at Almost Everything
Learning Curve: Symantec's OS X Threat Landscape
Learning Curve: Rootkits Roam the World of Windows
Industry Watch: For Apple, This is the Year That Wasn't

About | Buy Stuff | Industry Watch | Learning Curve | Products | Search | Twitter
Copyright © Rixstep. All rights reserved.