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

Commit 22881cce authored by Omar Eissa's avatar Omar Eissa
Browse files

Use quotactl to prevent abusive apps from using all inodes available

Make sure that we prevent malicious apps from causing DoS of the system
by exhausting all available inodes. Limit apps to not use more than 50%
of available inodes. This CL makes sure that upon app data preparation,
we use quotctl to see inodes hard limit for the app being prepared.

Test: atest CreateAppData_QuotaEnforcementSet
Bug: 293301664
Flag: android.installd.flags.enable_set_inode_quotas

Change-Id: Ia329aaf0b3c6c00f3d09a33d62e1131b30e4c2fa
parent 253826b0
Loading
Loading
Loading
Loading
+15 −0
Original line number Diff line number Diff line
@@ -7,6 +7,20 @@ package {
    default_applicable_licenses: ["frameworks_native_license"],
}

aconfig_declarations {
    name: "installd_flags",
    package: "android.installd.flags",
    container: "system",
    srcs: [
        "installd_flags.aconfig",
    ],
}

cc_aconfig_library {
    name: "installd_flags_c_lib",
    aconfig_declarations: "installd_flags",
}

cc_defaults {
    name: "installd_defaults",

@@ -50,6 +64,7 @@ cc_defaults {
        "server_configurable_flags",
    ],
    static_libs: [
        "installd_flags_c_lib",
        "libasync_safe",
        "libext2_uuid",
    ],
+8 −0
Original line number Diff line number Diff line
@@ -77,6 +77,8 @@
#include "QuotaUtils.h"
#include "SysTrace.h"

#include <android_installd_flags.h>

#ifndef LOG_TAG
#define LOG_TAG "installd"
#endif
@@ -89,6 +91,7 @@ using android::base::StringPrintf;
using android::base::unique_fd;
using android::os::ParcelFileDescriptor;
using std::endl;
namespace flags = android::installd::flags;

namespace android {
namespace installd {
@@ -882,6 +885,11 @@ binder::Status InstalldNativeService::createAppDataLocked(
            chown_app_profile_dir(packageName, appId, userId);
        }

        if (flags::enable_set_inode_quotas() &&
            !PrepareAppInodeQuota(uuid ? uuid->c_str() : "", uid)) {
            PLOG(ERROR) << "Failed to set hard quota " + path;
        }

        if (!prepare_app_profile_dir(packageName, appId, userId)) {
            return error("Failed to prepare profiles for " + packageName);
        }
+65 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@
#include <unordered_map>

#include <sys/quota.h>
#include <sys/statvfs.h>

#include <android-base/logging.h>

@@ -142,5 +143,69 @@ int64_t GetOccupiedSpaceForGid(const std::string& uuid, gid_t gid) {

}

bool PrepareAppInodeQuota(const std::string& uuid, uid_t uid) {
    const std::string device = FindQuotaDeviceForUuid(uuid);
    // Skip when device has no quotas present
    if (device.empty()) {
        return true;
    }

#if APPLY_HARD_QUOTAS
    struct dqblk dq;
    if (quotactl(QCMD(Q_GETQUOTA, USRQUOTA), device.c_str(), uid, reinterpret_cast<char*>(&dq)) !=
        0) {
        PLOG(WARNING) << "Failed to find quota for " << uid;
        return false;
    }

    if (dq.dqb_ihardlimit == 0) {
        auto path = create_data_path(uuid.empty() ? nullptr : uuid.c_str());
        struct statvfs stat;
        if (statvfs(path.c_str(), &stat) != 0) {
            PLOG(WARNING) << "Failed to statvfs " << path;
            return false;
        }

        dq.dqb_valid |= QIF_ILIMITS;
        // limiting the app to only 50% of the inodes available,
        // reducing the limit will be too restrictive especially for apps with valid use cases
        dq.dqb_ihardlimit = (stat.f_files / 2);

        if (quotactl(QCMD(Q_SETQUOTA, USRQUOTA), device.c_str(), uid,
                     reinterpret_cast<char*>(&dq)) != 0) {
            PLOG(WARNING) << "Failed to set hard quota for " << uid;
            return false;
        } else {
            LOG(DEBUG) << "Applied hard quotas for " << uid;
            return true;
        }
    } else {
        // Hard quota already set; assume it's reasonable
        return true;
    }
#else
    // Hard quotas disabled
    return true;
#endif
}

int64_t GetInodesQuotaHardLimitsForUid(const std::string& uuid, uid_t uid) {
    const std::string device = FindQuotaDeviceForUuid(uuid);
    if (device.empty()) {
        return 0;
    }

    struct dqblk dq;
    if (quotactl(QCMD(Q_GETQUOTA, USRQUOTA), device.c_str(), uid, reinterpret_cast<char*>(&dq)) !=
        0) {
        if (errno != ESRCH) {
            PLOG(ERROR) << "Failed to quotactl " << device << " for UID " << uid;
        }
        return -1;
    } else {
        return dq.dqb_ihardlimit;
    }
}

}  // namespace installd
}  // namespace android
+10 −0
Original line number Diff line number Diff line
@@ -37,6 +37,16 @@ int64_t GetOccupiedSpaceForGid(const std::string& uuid, gid_t gid);

/* Get the current occupied space in bytes for a project id or -1 if fails */
int64_t GetOccupiedSpaceForProjectId(const std::string& uuid, int projectId);

/**
 * Ensure that we have a hard-limit quota to protect against abusive apps;
 * they should never use more than 50% of inodes.
 */
bool PrepareAppInodeQuota(const std::string& uuid, uid_t uid);

/* Get the inode quota hard limits for a uid or -1 if fails */
int64_t GetInodesQuotaHardLimitsForUid(const std::string& uuid, uid_t uid);

}  // namespace installd
}  // namespace android

+10 −0
Original line number Diff line number Diff line
package: "android.installd.flags"
container: "system"

flag {
  name: "enable_set_inode_quotas"
  namespace: "mediaprovider"
  description: "This flag controls setting inode quota limits for apps"
  bug: "293301664"
  is_fixed_read_only: true
}
 No newline at end of file
Loading