Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 91ae4e39 authored by Victor Hsieh's avatar Victor Hsieh
Browse files

Add public API FileIntegrityManager.setupFsverity/getFsverityDigest

setupFsverity:

installd requires the requester to provide a proof that they own the
file. This is done by calling createFsveritySetupAuthToken with a
writable FD in the app process, when the API is called.

After that, the app process calls enableFsverity with the auth token to
actually enabling it.

To satisfy further requirements/checks by installed, the service also
passes extra information over.

getFsverityDigest:

The code runs in the app process. It sends ioctl FS_IOC_MEASURE_VERITY
to the filesystem. It is a read-only operation and does not change the
file state.

Together with the new allow rule in sepolicy, it introduced a new attack
surface to the kernel. This is low risk because the ioctl argument is
simple enough, and the command has also been fuzzed for years.
Therefore, the simple ioctl is preferred over alternatives like proxying
through system server.

Bug: 285185747
Bug: 296467543
Test: calling it from a testing app
Change-Id: I74881faadb359cc71061c0b5603977463787c0ad
parent 2ce97f2c
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -39349,8 +39349,10 @@ package android.security {
  }
  public final class FileIntegrityManager {
    method @FlaggedApi(Flags.FLAG_FSVERITY_API) @Nullable public byte[] getFsverityDigest(@NonNull java.io.File) throws java.io.IOException;
    method public boolean isApkVeritySupported();
    method @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public boolean isAppSourceCertificateTrusted(@NonNull java.security.cert.X509Certificate) throws java.security.cert.CertificateEncodingException;
    method @FlaggedApi(Flags.FLAG_FSVERITY_API) public void setupFsverity(@NonNull java.io.File) throws java.io.IOException;
  }
  public final class KeyChain {
+16 −0
Original line number Diff line number Diff line
@@ -20,8 +20,11 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.pm.UserInfo;
import android.os.IInstalld;
import android.os.IVold;
import android.os.ParcelFileDescriptor;

import java.io.IOException;
import java.util.List;
import java.util.Set;

@@ -185,4 +188,17 @@ public abstract class StorageManagerInternal {
    public abstract void prepareUserStorageForMove(String fromVolumeUuid, String toVolumeUuid,
            List<UserInfo> users);

    /**
     * A proxy call to the corresponding method in Installer.
     * @see com.android.server.pm.Installer#createFsveritySetupAuthToken()
     */
    public abstract IInstalld.IFsveritySetupAuthToken createFsveritySetupAuthToken(
            ParcelFileDescriptor authFd, int appUid, @UserIdInt int userId) throws IOException;

    /**
     * A proxy call to the corresponding method in Installer.
     * @see com.android.server.pm.Installer#enableFsverity()
     */
    public abstract int enableFsverity(IInstalld.IFsveritySetupAuthToken authToken, String filePath,
            String packageName) throws IOException;
}
+70 −0
Original line number Diff line number Diff line
@@ -16,12 +16,21 @@

package android.security;

import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.content.Context;
import android.os.IInstalld.IFsveritySetupAuthToken;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.system.ErrnoException;

import com.android.internal.security.VerityUtils;

import java.io.File;
import java.io.IOException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;

@@ -54,6 +63,67 @@ public final class FileIntegrityManager {
        }
    }

    /**
     * Enables fs-verity to the owned file under the calling app's private directory. It always uses
     * the common configuration, i.e. SHA-256 digest algorithm, 4K block size, and without salt.
     *
     * The operation can only succeed when the file is not opened as writable by any process.
     *
     * It takes O(file size) time to build the underlying data structure for continuous
     * verification. The operation is atomic, i.e. it's either enabled or not, even in case of
     * power failure during or after the call.
     *
     * Note for the API users: When the file's authenticity is crucial, the app typical needs to
     * perform a signature check by itself before using the file. The signature is often delivered
     * as a separate file and stored next to the targeting file in the filesystem. The public key of
     * the signer (normally the same app developer) can be put in the APK, and the app can use the
     * public key to verify the signature to the file's actual fs-verity digest (from {@link
     * #getFsverityDigest}) before using the file. The exact format is not prescribed by the
     * framework. App developers may choose to use common practices like JCA for the signing and
     * verification, or their own preferred approach.
     *
     * @param file The file to enable fs-verity. It should be an absolute path.
     *
     * @see <a href="https://www.kernel.org/doc/html/next/filesystems/fsverity.html">Kernel doc</a>
     */
    @FlaggedApi(Flags.FLAG_FSVERITY_API)
    public void setupFsverity(@NonNull File file) throws IOException {
        if (!file.isAbsolute()) {
            throw new IllegalArgumentException("Expect an absolute path");
        }
        IFsveritySetupAuthToken authToken;
        // fs-verity setup requires no writable fd to the file. Make sure it's closed before
        // continue.
        try (var authFd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE)) {
            authToken = mService.createAuthToken(authFd);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }

        try {
            int errno = mService.setupFsverity(authToken, file.getPath(),
                    mContext.getPackageName());
            if (errno != 0) {
                new ErrnoException("setupFsverity", errno).rethrowAsIOException();
            }
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Returns the fs-verity digest for the owned file under the calling app's
     * private directory, or null when the file does not have fs-verity enabled.
     *
     * @param file The file to measure the fs-verity digest.
     * @return The fs-verity digeset in byte[], null if none.
     * @see <a href="https://www.kernel.org/doc/html/next/filesystems/fsverity.html">Kernel doc</a>
     */
    @FlaggedApi(Flags.FLAG_FSVERITY_API)
    public @Nullable byte[] getFsverityDigest(@NonNull File file) throws IOException {
        return VerityUtils.getFsverityDigest(file.getPath());
    }

    /**
     * Returns whether the given certificate can be used to prove app's install source. Always
     * return false if the feature is not supported.
+7 −0
Original line number Diff line number Diff line
@@ -16,6 +16,9 @@

package android.security;

import android.os.ParcelFileDescriptor;
import android.os.IInstalld;

/**
 * Binder interface to communicate with FileIntegrityService.
 * @hide
@@ -23,4 +26,8 @@ package android.security;
interface IFileIntegrityService {
    boolean isApkVeritySupported();
    boolean isAppSourceCertificateTrusted(in byte[] certificateBytes, in String packageName);

    IInstalld.IFsveritySetupAuthToken createAuthToken(in ParcelFileDescriptor authFd);
    int setupFsverity(IInstalld.IFsveritySetupAuthToken authToken, in String filePath,
            in String packageName);
}
+20 −0
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.IInstalld.IFsveritySetupAuthToken;
import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
import static android.os.storage.OnObbStateChangeListener.ERROR_ALREADY_MOUNTED;
import static android.os.storage.OnObbStateChangeListener.ERROR_COULD_NOT_MOUNT;
@@ -4950,5 +4951,24 @@ class StorageManagerService extends IStorageManager.Stub
            }
        }

        @Override
        public IFsveritySetupAuthToken createFsveritySetupAuthToken(ParcelFileDescriptor authFd,
                int appUid, @UserIdInt int userId) throws IOException {
            try {
                return mInstaller.createFsveritySetupAuthToken(authFd, appUid, userId);
            } catch (Installer.InstallerException e) {
                throw new IOException(e);
            }
        }

        @Override
        public int enableFsverity(IFsveritySetupAuthToken authToken, String filePath,
                String packageName) throws IOException {
            try {
                return mInstaller.enableFsverity(authToken, filePath, packageName);
            } catch (Installer.InstallerException e) {
                throw new IOException(e);
            }
        }
    }
}
Loading