/* * patch_mgr.c - patch manager * * Copyright (C) 2016 Baidu, Inc. All Rights Reserved. * * You should have received a copy of license along with this program; * if not, ask for it from Baidu, Inc. * */ #include #include #include #include #include #include #include #include #include "patch_mgr.h" #include "util.h" #include "patch_file.h" #include "patch_info.h" #include "patch_base.h" #include "sysfs.h" #include "plts.h" #define FUNC_NAME_LEN_MAX 255 #define oases_uid_eq(a, b) ((a) == (b)) #define OASES_INVALID_UID (long)(-1) LIST_HEAD(patchinfo_list); int oases_check_patch_func(const char *name) { struct oases_patch_info *ctx, *c; struct oases_patch_entry *patch, *p; /* oases_mutex held */ list_for_each_entry_safe(ctx, c, &patchinfo_list, list) { list_for_each_entry_safe(patch, p, &ctx->patches, list) { if (!strncmp(name, kp_vtab(patch)->get_name(patch), FUNC_NAME_LEN_MAX - 1)) { oases_error("duplicate name: %s\n", name); return -EEXIST; } } } return 0; } int oases_check_patch(const char *id) { struct oases_patch_info *pos; struct oases_patch_info *n; /* oases_mutex held */ list_for_each_entry_safe(pos, n, &patchinfo_list, list) { if (strncmp(pos->id, id, PATCH_ID_LEN - 1) == 0) { oases_error("patch exist: %s\n", id); return -EEXIST; } } return 0; } void oases_free_patch(struct oases_patch_info *info) { struct oases_patch_entry *patch, *p; if (!info) { return; } list_for_each_entry_safe(patch, p, &info->patches, list) { kp_vtab(patch)->destroy(patch); list_del(&patch->list); kfree(patch->data); kfree(patch); } oases_patch_addr_free(&info->addresses); if (info->code_base) { vfree(info->code_base); } if (info->plog) kfree(info->plog); kfree(info); } int oases_op_patch(struct oases_patch_file *pfile) { int ret; struct oases_attack_log *plog; struct oases_patch_info *info = NULL; int (*code_entry)(struct oases_patch_info *info) = NULL; void (*init)(void) = NULL; info = kzalloc(sizeof(*info), GFP_KERNEL); if (!info) { oases_error("kzalloc info fail\n"); return -ENOMEM; } strncpy(info->id, pfile->pheader->id, PATCH_ID_LEN - 1); info->version = pfile->pheader->patch_version; info->status = STATUS_DISABLED; INIT_LIST_HEAD(&info->patches); plog = kzalloc(sizeof(struct oases_attack_log) * OASES_LOG_NODE_MAX, GFP_KERNEL); if (!plog) { oases_error("kzalloc log fail\n"); ret = -ENOMEM; goto fail_alloc_log;; } plog->uid = OASES_INVALID_UID; info->log_index = 1; info->plog = plog; spin_lock_init(&info->log_lock); ret = oases_build_code(info, pfile); if (ret < 0) { oases_error("oases_build_code fail\n"); goto fail_build_code; } code_entry = info->code_entry; ret = (*code_entry)(info); oases_debug("code_entry ret=%d\n", ret); if (ret != OASES_PATCH_SUCCESS) { if (ret > 0) { /* OASES_PATCH_FAILURE here */ ret = -EINVAL; } goto fail_entry; } oases_sysfs_init_patch(info); ret = oases_sysfs_add_patch(info); if (ret < 0) { oases_error("oases_sysfs_add_patch fail\n"); goto fail_sysfs; } info->attached = 1; list_add_tail(&info->list, &patchinfo_list); init = info->cbs.init; if (init) init(); return 0; fail_sysfs: oases_sysfs_del_patch(info); return ret; /* oases_register_patch() may succeed but entry() fail */ fail_entry: fail_build_code: fail_alloc_log: oases_free_patch(info); return ret; } static int op_unpatch(struct oases_patch_info *info) { int ret; void (*exit)(void) = NULL; ret = oases_remove_patch(info); if (ret) return ret; if (info->attached) { list_del(&info->list); info->attached = 0; } exit = info->cbs.exit; if (exit) exit(); oases_sysfs_del_patch(info); return 0; } int oases_op_unpatch(struct oases_unpatch *p) { int ret; struct oases_patch_info *pos; struct oases_patch_info *n; struct oases_unpatch oup; memset(&oup, 0, sizeof(struct oases_unpatch)); ret = copy_from_user(&oup, p, sizeof(*p)); if (ret) return -EFAULT; oup.id[PATCH_ID_LEN -1] = '\0'; list_for_each_entry_safe(pos, n, &patchinfo_list, list) { if (strncmp(oup.id, pos->id, PATCH_ID_LEN - 1) == 0) { if (pos->status == STATUS_ENABLED) { oases_error("error: patch enabled %s\n", pos->id); return -EINVAL; } return op_unpatch(pos); } } oases_error("warning: no patch: %s\n", oup.id); return -ENOENT; } static void increase_attack_count(struct oases_patch_info *ctx, long uid) { int i; struct oases_attack_log *entry; /* node 0 belongs to INVALID_UID */ if (oases_uid_eq(uid, OASES_INVALID_UID)) { entry = ctx->plog; entry->count++; entry->end_time = get_seconds(); if (!entry->start_time) entry->start_time = entry->end_time; return; } for (i = 1; i < ctx->log_index; i++) { entry = ctx->plog + i; if (oases_uid_eq(entry->uid, uid)) { entry->count++; entry->end_time = get_seconds(); return; } } /* new uid */ entry = ctx->plog + ctx->log_index; entry->uid = uid; entry->count = 1; entry->start_time = get_seconds(); entry->end_time = entry->start_time; ctx->log_index++; } void oases_attack_logger(struct oases_patch_info *ctx) { long uid = OASES_INVALID_UID; unsigned long flags; spin_lock_irqsave(&ctx->log_lock, flags); if (ctx->log_index < OASES_LOG_NODE_MAX) uid = sys_getuid(); increase_attack_count(ctx, uid); spin_unlock_irqrestore(&ctx->log_lock, flags); }