Looking at the previously linked changelog, one can notice the following fix in the vold daemon:

Project: platform/system/vold
0de7c61 : Validate asec names.

Vold (Volume Management Daemon) is a daemon running as root, which main goal is to handle removable media devices. ASEC are secured containers allowing applications to securely store data on the SD card, and have been introduced back in 2010. These containers have been created because the SD Card filesystem (VFAT) does not allow privileges separation.

ASEC are simple files containing a ciphered filesystem, which is unciphered and mounted with permissions for a specific UID (and therefore for a specific application). The ASEC files (extension “.asec”) are stored on the SD Card or in the “/data” folder, and mounted in a subfolder of “/mnt/asec”.

Vulnerability

The creation of the ASEC file is performed by the following code, in VolumeManager.cpp:

const char *asecDir = isExternal ? 
                  Volume::SEC_ASECDIR_EXT : Volume::SEC_ASECDIR_INT;
int written = snprintf(asecFileName, sizeof(asecFileName),
                  "%s/%s.asec", asecDir, id);

The mount point for the newly created asec is then defined as follows:

int written = snprintf(mountPoint, sizeof(mountPoint), 
                  "%s/%s", Volume::ASECDIR, id);

The vulnerability here is rather obvious: there is no check on the “id” variable, which is the name given by the user to its ASEC container. It is therefore possible to perform a basic path traversal, to create the ASEC file and its mount point in a different directory than expected, as for example one the “attacker” can write into.

The following code is then responsible for the creation of the mount point:

if (mkdir(mountPoint, 0000)) {
  if (errno != EEXIST) {
    SLOGE("Mountpoint creation failed (%s)", strerror(errno));
    if (cleanupDm) {
      Devmapper::destroy(idHash);
    }
    Loop::destroyByDevice(loopDevice);
    unlink(asecFileName);
    return -1;
  }
}
[...]
mountStatus = xxx::doMount(dmDevice, mountPoint, false, 
                 false, false, ownerUid, 0, 0000, false);

This means that if the mount point already exists, no error is raised, and the container is correctly mounted in “mountPoint”. Guess what? If “mountPoint” already exists AND is a symlink to an existing directory, the ASEC container will be mounted over this directory. And the user will have full access to it, allowing him to write new files inside.

There are many ways of exploiting this vulnerability to gain root privileges.

Last detail about this vulnerability: it requires permissions to create ASEC containers. The “shell” user, as used by adb, has the requiered privileges. For the vulnerability to be exploited from an application, it needs the ASEC_* permissions (such as ASEC_CREATE).

The Fix

Google has now added a call to a new function “isLegalAsecId()” at the beginning of each function dealing with ASEC ids. The code of the function is the following:

bool VolumeManager::isLegalAsecId(const char *id) const {
  size_t i;
  size_t len = strlen(id);

  if (len == 0) {
    return false;
  }
  if ((id[0] == '.') || (id[len - 1] == '.')) {
    return false;
  }

  for (i = 0; i < len; i++) {
    if (id[i] == '.') {
      // i=0 is guaranteed never to have a dot. See above.
      if (id[i-1] == '.') return false;
      continue;
    }
    if (id[i] == '_' || id[i] == '-') continue;
    if (id[i] >= 'a' && id[i] <= 'z') continue;
    if (id[i] >= 'A' && id[i] <= 'Z') continue;
    if (id[i] >= '0' && id[i] <= '9') continue;
    return false;
  }

  return true;
}

This forbids the use of “..” and “/” in the ASEC ids, which fixes the path traversal attacks.

Conclusion

Google has fixed a very old local root vulnerability. We have evidences that this vulnerability was known, and it might already have been exploited in the wild by bad guys. Android is becoming more and more secure as Google introduces modern security mechanisms, but old code still needs to be fully audited to eradicate these 90’s security vulnerabilities.