* Copyright (c) 2026-2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "appspawndf_utils.h"
#include <atomic>
#include <fstream>
#include <mutex>
#include <unordered_map>
#include <unordered_set>
#include <sstream>
#include <string>
#include <sys/random.h>
#include <sys/stat.h>
#include "appspawn_utils.h"
#include "config_policy_utils.h"
#include "parameters.h"
#include "json_utils.h"
#include "securec.h"
namespace OHOS {
namespace AppSpawn {
constexpr const char* APPSPAWNDF_LIST_PATH = "etc/memmgr/appspawndf_process.json";
constexpr const char* APPSPAWN_DF_APPS = "appspawndf_apps";
constexpr const char* GWP_ASAN_ENABLE = "gwp_asan.enable.app.";
const std::string LOCAL_PATH = "/system/etc/SwitchOffList/";
const std::string CLOUD_PATH = "/data/service/el1/public/update/param_service/install/system/etc/SwitchOffList/";
const std::string APPSPAWN_DF_SWITCH_OFF_FLAG = "appspawn_df_switch_off_flag_v_0";
constexpr int32_t VERSION_LEN = 4;
constexpr int32_t GWP_ASAN_NAME_LEN = 256;
static std::atomic<uint8_t> g_processSampleRate = 128;
static std::atomic<uint8_t> g_previousRandomValue = 0;
constexpr int32_t MAX_VERSION_FILE_LEN = 1024 * 5;
constexpr int32_t MAX_SWITCH_OFF_FILE_LEN = 1024 * 5;
class AppSpawnDfUtils {
public:
static AppSpawnDfUtils &GetInstance()
{
static AppSpawnDfUtils instance;
return instance;
}
bool IsEnabled(AppSpawnClientInitPtr initFunc)
{
std::lock_guard<std::mutex> lock(listMutex_);
if (!isLoaded_) {
LoadWhiteList();
if (!whiteList_.empty() && initFunc != nullptr) {
initFunc_ = initFunc;
}
isLoaded_ = true;
}
return !whiteList_.empty();
}
AppSpawnClientHandle GetHandle()
{
std::lock_guard<std::mutex> lock(handleMutex_);
if (dfHandle_ == nullptr && initFunc_ != nullptr) {
int ret = initFunc_(APPSPAWNDF_SERVER_NAME, &dfHandle_);
APPSPAWN_CHECK(ret == 0, return nullptr,
"Appspawn df client failed to init, ret=%{public}d", ret);
}
return dfHandle_;
}
bool IsInWhiteList(const char *name)
{
if (name == nullptr || *name == '\0') {
return false;
}
std::lock_guard<std::mutex> lock(listMutex_);
return whiteList_.count(name) > 0;
}
bool IsBroadcast(uint32_t msgType)
{
static const std::unordered_set<uint32_t> bcastMsgs = {
MSG_LOCK_STATUS, MSG_UNINSTALL_DEBUG_HAP, MSG_OBSERVE_PROCESS_SIGNAL_STATUS,
MSG_UNLOAD_WEBLIB_IN_APPSPAWN, MSG_LOAD_WEBLIB_IN_APPSPAWN
};
return bcastMsgs.find(msgType) != bcastMsgs.end();
}
bool AppSpawndfIsAppEnableGWPASan(const char *name, AppSpawnMsgFlags* msgFlags)
{
if (name == nullptr || *name == '\0' || msgFlags == NULL) {
return false;
}
if (CheckAppSpawnMsgFlag(msgFlags, APP_FLAGS_GWP_ENABLED_FORCE)) {
return true;
}
char paraName[GWP_ASAN_NAME_LEN + 1] = {0};
int ret = sprintf_s(paraName, GWP_ASAN_NAME_LEN + 1, "%s%s", GWP_ASAN_ENABLE, name);
if (ret >= 0 && ret <= GWP_ASAN_NAME_LEN && system::GetBoolParameter(paraName, false)) {
return true;
}
uint8_t randomValue;
if (getrandom(&randomValue, sizeof(randomValue), GRND_RANDOM | GRND_NONBLOCK) == -1) {
randomValue = g_previousRandomValue.fetch_add(1, std::memory_order_relaxed) + 1;
}
if ((randomValue % g_processSampleRate.load()) == 0) {
SetAppSpawnMsgFlags(msgFlags, APP_FLAGS_GWP_ENABLED_FORCE);
return true;
}
return false;
}
int SetAppSpawnMsgFlags(AppSpawnMsgFlags *msgFlags, uint32_t index)
{
uint32_t blockIndex = index / 32;
uint32_t bitIndex = index % 32;
if (blockIndex < msgFlags->count) {
msgFlags->flags[blockIndex] |= (1 << bitIndex);
}
return 0;
}
private:
static inline void Trim(std::string& inputStr)
{
if (inputStr.empty()) {
return;
}
inputStr.erase(inputStr.begin(), std::find_if(inputStr.begin(), inputStr.end(),
[](unsigned char ch) { return !std::isspace(static_cast<unsigned char>(ch)); }));
inputStr.erase(std::find_if(inputStr.rbegin(), inputStr.rend(),
[](unsigned char ch) { return !std::isspace(static_cast<unsigned char>(ch)); }).base(), inputStr.end());
}
static inline bool IsNumber(const std::string &str)
{
return !str.empty() && std::all_of(str.begin(), str.end(), [](unsigned char c) {
return std::isdigit(c);
});
}
std::vector<std::string> SplitString(const std::string& str, char pattern)
{
std::stringstream iss(str);
std::vector<std::string> result;
std::string token;
while (getline(iss, token, pattern)) {
result.emplace_back(token);
}
return result;
}
std::vector<std::string> GetVersionNums(const std::string& filePath)
{
struct stat fileStat;
if (stat(filePath.c_str(), &fileStat) != 0 ||
fileStat.st_size <= 0 || fileStat.st_size > MAX_VERSION_FILE_LEN) {
APPSPAWN_LOGV("AppSpawnDfUtils::GetVersionNums: stat failed");
return {};
}
std::ifstream file(filePath);
if (!file.is_open()) {
return {};
}
std::string line;
std::getline(file, line);
file.close();
if (line.empty()) {
APPSPAWN_LOGE("AppSpawnDfUtils::GetVersionNums line is empty!");
return {};
}
std::vector<std::string> versionStr = SplitString(line, '=');
const size_t expectedSize = 2;
if (versionStr.size() != expectedSize) {
APPSPAWN_LOGE("AppSpawnDfUtils::GetVersionNums: version num is not valid, expected format 'key=value'.");
return {};
}
if (versionStr[1].empty()) {
APPSPAWN_LOGE("AppSpawnDfUtils::GetVersionNums: version value is empty.");
return {};
}
Trim(versionStr[1]);
std::vector<std::string> versionNum = SplitString(versionStr[1], '.');
return versionNum;
}
bool CompareVersion(const std::vector<std::string> &localVersion, const std::vector<std::string> &cloudVersion)
{
if (localVersion.size() != VERSION_LEN || cloudVersion.size() != VERSION_LEN) {
APPSPAWN_LOGI("AppSpawnDfUtils::CompareVersion: version num is not valid, use localVersion.");
return false;
}
for (int32_t i = 0; i < VERSION_LEN; i++) {
if (localVersion[i] != cloudVersion[i]) {
if (!IsNumber(localVersion[i]) || !IsNumber(cloudVersion[i])) {
APPSPAWN_LOGE("AppSpawnDfUtils::CompareVersion: version part contains non-numeric characters.");
return false;
}
int32_t localValue = std::atoi(localVersion[i].c_str());
int32_t cloudValue = std::atoi(cloudVersion[i].c_str());
return localValue < cloudValue;
}
}
return false;
}
std::string GetHigherVersionPath()
{
std::string localVersionFile = LOCAL_PATH + "version.txt";
std::string cloudVersionFile = CLOUD_PATH + "version.txt";
std::vector<std::string> localVersionNums = GetVersionNums(localVersionFile);
std::vector<std::string> cloudVersionNums = GetVersionNums(cloudVersionFile);
if (CompareVersion(localVersionNums, cloudVersionNums)) {
return CLOUD_PATH;
}
return LOCAL_PATH;
}
bool IsDisableDfmalloc()
{
std::string filePath = GetHigherVersionPath() + "switch_off_list";
struct stat fileStat;
if (stat(filePath.c_str(), &fileStat) != 0 ||
fileStat.st_size <= 0 || fileStat.st_size > MAX_SWITCH_OFF_FILE_LEN) {
APPSPAWN_LOGV("AppSpawnDfUtils::IsDisableDfmalloc: stat failed");
return false;
}
std::ifstream inputFile(filePath);
if (!inputFile.is_open()) {
APPSPAWN_LOGI("AppSpawnDfUtils::IsDisableDfmalloc: Can not open the file.");
return false;
}
std::string line;
while (std::getline(inputFile, line)) {
Trim(line);
if (line == APPSPAWN_DF_SWITCH_OFF_FLAG) {
APPSPAWN_LOGI("AppSpawnDfUtils::IsDisableDfmalloc: close dfmalloc");
inputFile.close();
return true;
}
}
inputFile.close();
return false;
}
void LoadWhiteList()
{
if (IsDisableDfmalloc()) {
return;
}
char buf[PATH_MAX] = {0};
char *path = GetOneCfgFile(APPSPAWNDF_LIST_PATH, buf, PATH_MAX);
if (path == nullptr) {
return;
}
cJSON *root = GetJsonObjFromFile(path);
if (root == nullptr) {
return;
}
cJSON *arr = cJSON_GetObjectItemCaseSensitive(root, APPSPAWN_DF_APPS);
if (cJSON_IsArray(arr)) {
cJSON *item = nullptr;
cJSON_ArrayForEach(item, arr) {
const char *val = cJSON_GetStringValue(item);
if (val != nullptr) {
whiteList_.insert(val);
}
}
}
cJSON_Delete(root);
}
inline int CheckAppSpawnMsgFlag(const AppSpawnMsgFlags *msgFlags, uint32_t index)
{
if (!msgFlags) {
return 0;
}
uint32_t blockIndex = index / 32;
uint32_t bitIndex = index % 32;
if (blockIndex < msgFlags->count) {
return CHECK_FLAGS_BY_INDEX(msgFlags->flags[blockIndex], bitIndex);
}
return 0;
}
bool isLoaded_ = false;
std::unordered_set<std::string> whiteList_;
AppSpawnClientInitPtr initFunc_ = nullptr;
std::mutex listMutex_;
AppSpawnClientHandle dfHandle_ = nullptr;
std::mutex handleMutex_;
};
}
}
extern "C" {
bool AppSpawndfIsServiceEnabled(AppSpawnClientInitPtr initFunc)
{
return OHOS::AppSpawn::AppSpawnDfUtils::GetInstance().IsEnabled(initFunc);
}
AppSpawnClientHandle AppSpawndfGetHandle(void)
{
return OHOS::AppSpawn::AppSpawnDfUtils::GetInstance().GetHandle();
}
bool AppSpawndfIsAppInWhiteList(const char *name)
{
return OHOS::AppSpawn::AppSpawnDfUtils::GetInstance().IsInWhiteList(name);
}
bool AppSpawndfIsAppEnableGWPASan(const char *name, AppSpawnMsgFlags* msgFlags)
{
return OHOS::AppSpawn::AppSpawnDfUtils::GetInstance().AppSpawndfIsAppEnableGWPASan(name, msgFlags);
}
bool AppSpawndfIsBroadcastMsg(uint32_t type)
{
return OHOS::AppSpawn::AppSpawnDfUtils::GetInstance().IsBroadcast(type);
}
void AppSpawndfMergeBroadcastResult(uint32_t msgType, AppSpawnResult *mainRes,
AppSpawnResult *dfRes)
{
if (mainRes == nullptr || dfRes == nullptr) {
return;
}
if (msgType == MSG_UNINSTALL_DEBUG_HAP || msgType == MSG_LOCK_STATUS) {
if (mainRes->result != 0 && dfRes->result == 0) {
mainRes->result = 0;
mainRes->pid = dfRes->pid;
}
} else {
if (mainRes->result != 0 || dfRes->result != 0) {
if (mainRes->result == 0) {
mainRes->result = (dfRes->result != 0) ? dfRes->result : -1;
}
}
}
}
int SetAppSpawnMsgFlags(AppSpawnMsgFlags *msgFlags, uint32_t index)
{
return OHOS::AppSpawn::AppSpawnDfUtils::GetInstance().SetAppSpawnMsgFlags(msgFlags, index);
}
}