Tamper-Resistant Mac OS X App-Bundles

Last revised: 10Aug2003 GLG

Table of Contents

Introduction

You should be somewhat familiar with the internal structure of an app-bundle (its anatomy), especially as far as Java programs are concerned. You should understand Mac OS X's file permissions and file ownership, and what it means for file accessibility.

Trustworthiness

What does "trustworthy" mean?
    trustworthy, adj. -- worthy of trust or confidence.

One simple aspect of trustworthiness is mere repeatability. Is this application the same application I used before? Is this document the same document I read or edited before? Does pressing this button do the same thing it did before? Unexpected changes do not engender trustworthiness.

This does not mean change is antithetical to trustworthiness. Some things are only trustworthy when they do change, and are untrustworthy when they don't: a car's odometer, an interest-earning bank account, a random-number generator, a wristwatch. The difference is that those things are expected to change. So a key part of being able to judge trustworthiness is knowledge -- knowing what should change and what shouldn't, as well as when change should occur and when it shouldn't.

Consider the following examples:

  1. A bundled Java app uses AuthKit to perform some privileged task. How do you know the jars and JNI-libs in the app haven't been changed?
  2. You distribute a bundled Java app that uses AuthKit to perform some privileged task. Users install it and use it on their machine. How do they know the app-bundle they installed won't unexpectedly change?
  3. A bundled Java app keeps a restricted-access database within itself, such as a list of trusted signing certificates, and uses AuthKit to restrict database changes only to administrative users. How do you, as the developer, structure the app-bundle's contents and equip the database files to limit access in this way?

All these examples, and many others, come down to forbidding certain changes while allowing others. In short, resistance to unexpected or unauthorized change:

The superuser (root) on any Unix-based or Unix-like OS is always presumed to be trustworthy. The superuser can always access any file, directory, or device, even when the permission-bits stored for the item do not overtly grant that permission. That is, superuser can always read or write any file or directory, even when all the permission-bits are cleared and would seem to deny all access. The superuser can also change the permissions and the ownership of any file or directory, even ones not owned by root.

For all practical purposes, then, any user or program acting as root is omnipotent (all-powerful). Therefore, any programmed actions that will eventually be done while acting as root must be protected from unexpected or unauthorized change. The only way to do this on Unix-based or Unix-like OS'es, including Mac OS X, is to set restricted permissions and then assign ownership to root. Setting restricted permissions defines the accessibility of the item, and assigning ownership to root prevents anyone but root from making any further changes to the permissions.

Applications

A trustworthy application should not change unless you decide to change it. Even if it's a self-updating application, it should only change under your control, or the control of someone entrusted to change it. However, just because an application is unchanging doesn't mean it's trustworthy. An unchanging program can steal passwords or send private financial data around the internet, so just because a programs is unchanging doesn't mean its actions are trustworthy.

The default applications provided with Mac OS X are located in the /Applications folder. All these applications are owned by root (shown as "system" in the Finder's Get Info box). They can only be written (changed) by the owner (root) or by a member of the 'admin' group. That is, by someone who is trustworthy (or is presumed to be).

An application that can be changed by someone who isn't trustworthy is an untrustworthy application. Not because of what it does, but because of what it could be changed to do. For example, someone could replace images or text in the application, misleading a user about what it's doing, or causing a user to do something unintended. Or depending on how your machine is set up, "someone" could be a remote program or person connecting to your computer from anywhere on the network or the internet. Considering the number of malicious people and programs out there, that could be a very bad thing indeed.

It's even more important for certain kinds of applications to remain trustworthy: the ones that execute as root. One such kind of program is called a "setuid-root" program. It is owned by root and has a special permission-bit set, that can only be set when acting as root. Some native Unix commands are setuid-root, and must be that way in order to work. Java programs aren't normally setuid-root, so we'll leave it at that.

However, the other kind of program is one that launches another process as root, after the user is authenticated as a member of the admin group. This includes Java programs that use AuthKit, as well as native programs that use Mac OS X's Authorization Services, or command-line tools that run under 'sudo'.

These kind of programs are less obviously vulnerable because the files that make up the application (JARs, JNI-libs, resources, etc.) have no special ownership or indication of privilege, unlike setuid-root programs which have a special permission-bit on the file itself. These programs look just like any other program. The crucial difference is that because they contain code that eventually executes as root, everything leading up to root execution needs to be trustworthy, with at least the same level of tamper-resistance as if it were a setuid-root program. If it's not, then something could be surreptitiously inserted that would wreak havoc when eventually executed as root. So it's not just the part that executes as root that matters, it's the entire "chain of provenance" leading up to root execution that must be protected from tampering or compromise.

Maintaining Trustworthy Applications

For an application to remain trustworthy, it must resist unauthorized or unexpected change. The simplest thing is to lock it down so only the most trustworthy and powerful entity on the computer (i.e. the root user) can change it. In particular, revoke all write permissions then assign its ownership to root, so only root can change the permissions thereafter.

This lockdown must occur after an application has been installed. The initial installation is itself presumed to be trustworthy, and the data being installed is presumed to come from a trustworthy source. If that's not true, then lockdown will prevent further unexpected change, but the original installation itself will be suspect, so overall trustworthiness will still be in question.

For most applications, a lockdown can revoke all write permissions on all files and directories in the app-bundle. That's because most applications do not write to any file or directory within the app-bundle structure. There are occasional exceptions, though, where an application maintains a trusted database within itself and only allows certain users or members of a group to update that database. In general, though, forbidding all writing is an acceptable way to prevent changes to an app-bundle.

Giving ownership to root prevents anyone but root from changing the permissions. Without this step, whoever owned the files or directories in the app-bundle could change the permissions to allow writing, and then back again to fool you into thinking no change had been made. Root ownership is essential.

SPECIAL CONSIDERATIONS

Some kinds of app-bundles need special consideration when being locked down: If Authorization Services is used to control access to a helper program, it can be done with an additional specific authorization rule, or the default "execute as root" rule can be used. It is better to use a specifically named privilege (called a "right" in AuthSvcs terminology), even if Authorization Services ends up using its default rule to grant the privilege. This gives users the option of defining a rule if they wish, and if they have the knowledge to edit the Authorization Services rule database. If you always want more restricted behavior than the default rule provides, you can carefully control the lifetime of the granted privilege by controlling the lifetime of the AuthKit Authorization object, or the underlying session it's attached to.

What Lockdown WON'T Do

Locking down an application says nothing about its original trustworthiness. You don't lock something down to provide proof of initial trustworthiness. You only lock it down to prevent unauthorized changes that would compromise its initial trustworthiness. If what you're locking down isn't trustworthy to begin with, then locking it down won't make it trustworthy. It will just prevent changes to it.

A program designed to steal your address book or keychain and send it to a remote URL is untrustworthy. It is no more trustworthy because it's locked down. Lockdown means nothing if the program itself is inherently a sneaky, conniving, thieving, lying, corrupted piece of infected or malicious software. All the lockdown does is prevent any unexpected future changes. You'll still have a sneaky, conniving, etc. piece of mal-ware. You just won't have any unexpected changes made to it.

Distributing Locked-Down Applications

A convenient format for distributing app-bundles is a compressed read-only disk-image. The application is then executable directly from the disk-image, which is mounted simply by double-clicking. Users are then just two double-clicks away from running the application, with no intervening installs to wade through.

An application on a disk-image is only as trustworthy as the disk-image file. You can make a disk-image be read-only, but an application can still be copied to a user's hard-disk or to a writable disk-image. When a locked-down app-bundle is copied, the copy is no longer locked down. In fact, the Finder will make all the directories in the app-bundle writable by anyone, by default (I consider this a Finder bug). Furthermore, the user who made the copy will own the app-bundle's contents, so can do with it what he may.

You can distribute an application on a disk-image and make it uncopiable except by root. This is a simple obstacle for experienced users to overcome, but they must do so by acting as root, thereby maintaining the tamper-resistance that you originally distributed under. If only root can make a trustworthy copy, then any copies are trustworthy.

Before describing how to make an app-bundle uncopiable, we must first examine how different disk-image formats handle permissions and ownership. They don't all work the same way, and picking the wrong format can unexpectedly change what the actual working permissions and ownership are. As already noted above, "unexpected" and "trustworthy" don't mix.

The Disk Copy utility (/Applications/Utilities/Disk Copy) can create disk-images in any of several formats. The command-line program 'hdiutil' can do the same (see 'man hdiutil' for details). Each format provides some amount of support for Unix-style file permissions and ownership, even if it's entirely faked and appears accessible to everyone. The formats and their support for permissions and ownership are:

Mac OS Extended (HFS+)
Permissions are real, but ownership is faked. Ownership always appears as the user who mounted the disk-image. This format is useless for locked down applications, because the owner won't appear to be root.
Mac OS Standard (HFS)
All permissions and ownership are faked. Permissions always appear as all-read, all-write, all-execute. Ownership always appears as the user who mounted the disk-image. This format is useless for locked down applications.
MS-DOS File System (FAT)
All permissions and ownership are faked. Permissions always appear as all-read, all-write, all-execute. Ownership always appears as the user who mounted the disk-image. This format is useless for locked down applications.
Unix File System (UFS)
All permissions and ownership are real. This format is the only one that completely works for locked down uncopiable applications.
When a UFS disk-image is created, it is initially owned by root and is writable only by root. You can change the permissions to allow writing by everyone. You can do this with 'sudo chown' or by using the Finder (10.2+) and a Get Info box's Ownership & Permissions sub-pane. You will have to authenticate to do this.

To make an app-bundle uncopiable, go into the app-bundle and make its Contents/Resources/Java/ sub-directory unreadable. You can do this with chmod at the command-line, or you can use the Finder's Get Info box to turn it into a Drop-Box for everyone. You should do this BEFORE locking down the app-bundle, otherwise you'll have to become root in order to change the read-permissions.

Once this is done, the Finder will not copy the app-bundle, because it can't copy the contents of the unreadable directory. Thus, you should only make the app-bundle uncopiable after you've copied it onto your distribution disk-image. You can then run AppBundleLockdown on the disk-image application, so it gets locked down in place.

Although you will no longer be able to read or list the contents of Contents/Resources/Java, the program will still work, because the directory still has all search permissions. Thus, pathnames that refer to files in that directory (e.g. in the classpath) will still work.

Forbidding read-access to prevent copying also works when an app-bundle is on a normal hard-disk volume. Such an app-bundle can still be moved around on the disk, if the enclosing directory allows writing. It can't be copied to another disk, or replicated on its current disk, unless the user is acting as root.

Despite lacking read-permissions on a directory, Disk Copy can still convert a disk-image with a locked-down uncopiable app-bundle. So you can still turn a R/W disk-image into a compressed R/O one for distribution. Disk Copy can also convert a disk-image back to a R/W form, but the permissions and ownership will still be locked down, so the app-bundle will still be uncopiable except by root.

How to Lock Things Down

The Finder can't do it. It doesn't have enough capabilities.

No single command-line can do it, either, but a pair of commands can: chmod to change the permissions, and chown to change the ownership.

In any case, you must be root (or acting as root) in order for chown to work. So from the command-line, you also have to use the sudo command and enter your admin password when prompted.

If you chown a file or directory, you must ensure that any setuid bit present is cleared first. This ensures that a setuid-someone file doesn't turn into a setuid-root file when its ownership changes to root.

Command-lines aren't for everyone, and 'sudo' doesn't work too well if you aren't running a command-line shell where you can type in a password to its terminal-oriented prompt.

The AppBundleLockdown tool can lock down any accessible app-bundle, and works in either a command-line environment or as a GUI application. You can also read its source files to see how AuthKit can be used in a simple real-world application.


To Greg's Home Page
To Greg's Software Page