| /* |
| * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 |
| * as published by the Free Software Foundation. |
| */ |
| |
| #include <asm/uaccess.h> |
| |
| #include <linux/dsms.h> |
| #include <linux/init.h> |
| #include <linux/kernel.h> |
| #include <linux/kmod.h> |
| #include <linux/module.h> |
| #include <linux/proc_fs.h> |
| #include <linux/slab.h> |
| #include <linux/string.h> |
| |
| #include "dsms_access_control.h" |
| #include "dsms_debug.h" |
| #include "dsms_init.h" |
| |
| #define VALUE_STRLEN (22) |
| |
| // Command: <<DSMS_BINARY>> <<feature_code>> <<detail>> <<value>> |
| #define DSMS_BINARY "/system/bin/dsms" |
| static const char *dsms_command[] = { |
| DSMS_BINARY, |
| NULL, |
| NULL, |
| NULL, |
| NULL |
| }; |
| #define FEATURE_INDEX (1) |
| #define EXTRA_INDEX (2) |
| #define VALUE_INDEX (3) |
| |
| #define MESSAGE_COUNT_LIMIT (50) |
| |
| static const char *dsms_environ[] = { |
| "HOME=/", |
| "PATH=/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin", |
| "ANDROID_DATA=/data", |
| NULL |
| }; |
| |
| static atomic_t message_counter = ATOMIC_INIT(0); |
| |
| static char *dsms_alloc_user_string(const char *string) |
| { |
| size_t size; |
| char *string_cpy; |
| if (string == NULL || *string == 0) |
| return ""; |
| |
| size = strnlen(string, PAGE_SIZE - 1) + 1; |
| string_cpy = (char *) kmalloc(size * sizeof(string[0]), |
| GFP_USER); |
| if (string_cpy) { |
| memcpy(string_cpy, string, size); |
| string_cpy[size - 1] = '\0'; |
| } |
| |
| return string_cpy; |
| } |
| |
| static char *dsms_alloc_user_value(int64_t value) |
| { |
| char *string = (char *) kmalloc(VALUE_STRLEN, GFP_USER); |
| if (string) { |
| snprintf(string, VALUE_STRLEN, "%lld", value); |
| string[VALUE_STRLEN-1] = 0; |
| } |
| return string; |
| } |
| |
| static void dsms_free_user_string(const char *string) |
| { |
| if (string == NULL || *string == 0) |
| return; |
| kfree(string); |
| } |
| |
| static void dsms_message_cleanup(struct subprocess_info *info) |
| { |
| if (info && info->argv) { |
| dsms_free_user_string(info->argv[FEATURE_INDEX]); |
| dsms_free_user_string(info->argv[EXTRA_INDEX]); |
| dsms_free_user_string(info->argv[VALUE_INDEX]); |
| kfree(info->argv); |
| } |
| atomic_dec(&message_counter); |
| } |
| |
| static int check_recovery_mode(void) |
| { |
| static int recovery = -1; |
| struct file *fp; |
| |
| if (recovery < 0) { |
| fp = filp_open("/sbin/recovery", O_RDONLY, 0); |
| if (IS_ERR(fp)) { |
| printk(KERN_ALERT DSMS_TAG "Normal mode.\n"); |
| recovery = 0; |
| } else { |
| filp_close(fp, NULL); |
| printk(KERN_ALERT DSMS_TAG "Recovery mode, DSMS is disabled.\n"); |
| recovery = 1; |
| } |
| } |
| return (recovery > 0); |
| } |
| |
| static int dsms_send_allowed_message(const char *feature_code, |
| const char *detail, |
| int64_t value) |
| { |
| char **argv; |
| struct subprocess_info *info; |
| int ret; |
| |
| // limit number of message to prevent message's bursts |
| if (atomic_add_unless(&message_counter, 1, MESSAGE_COUNT_LIMIT) == 0) { |
| printk(DSMS_TAG "Message counter has reached its limit."); |
| ret = -EBUSY; |
| goto limit_error; |
| } |
| // allocate argv, envp, necessary data |
| argv = (char**) kmalloc(sizeof(dsms_command), GFP_USER); |
| if (!argv) { |
| printk(DSMS_TAG "Failed memory allocation for argv."); |
| ret = -ENOMEM; |
| goto no_mem_error; |
| } |
| |
| memcpy(argv, dsms_command, sizeof(dsms_command)); |
| |
| argv[FEATURE_INDEX] = dsms_alloc_user_string(feature_code); |
| argv[EXTRA_INDEX] = dsms_alloc_user_string(detail); |
| argv[VALUE_INDEX] = dsms_alloc_user_value(value); |
| if (!argv[FEATURE_INDEX] || !argv[EXTRA_INDEX] || |
| !argv[VALUE_INDEX]) { |
| printk(DSMS_TAG "Failed memory allocation for user string."); |
| ret = -ENOMEM; |
| goto no_mem_error; |
| } |
| |
| // call_usermodehelper with wait_proc and callback function to cleanup data after execution |
| info = call_usermodehelper_setup(DSMS_BINARY, argv, |
| (char**) dsms_environ, |
| GFP_ATOMIC, NULL, |
| &dsms_message_cleanup, NULL); |
| if (!info) { |
| printk(DSMS_TAG "Failed memory allocation for" |
| "call_usermodehelper_setup."); |
| ret = -ENOMEM; |
| goto no_mem_error; |
| } |
| |
| return call_usermodehelper_exec(info, UMH_NO_WAIT); |
| |
| no_mem_error: |
| if (argv) { |
| dsms_free_user_string(argv[FEATURE_INDEX]); |
| dsms_free_user_string(argv[EXTRA_INDEX]); |
| dsms_free_user_string(argv[VALUE_INDEX]); |
| kfree(argv); |
| } |
| atomic_dec(&message_counter); |
| |
| limit_error: |
| return ret; |
| } |
| |
| int noinline dsms_send_message(const char *feature_code, |
| const char *detail, |
| int64_t value) |
| { |
| void *address; |
| int ret; |
| |
| #ifdef DSMS_KERNEL_ENG /* skip dsms at eng binary */ |
| printk(KERN_ALERT DSMS_TAG "It's ENG binary, skip..."); |
| return DSMS_SUCCESS; |
| #endif /* DSMS_KERNEL_ENG */ |
| |
| // check recovery mode on, dsms doesn't work in recovery mode |
| if (check_recovery_mode()) { |
| printk(KERN_ALERT DSMS_TAG "Recovery mode, DSMS is disabled."); |
| return DSMS_SUCCESS; |
| } |
| |
| #ifdef DSMS_DEBUG_TRACE_DSMS_CALLS |
| dsms_debug_message(feature_code, detail, value); |
| #endif //DSMS_DEBUG_TRACE_DSMS_CALLS |
| |
| if (!dsms_is_initialized()) { |
| #ifdef DSMS_DEBUG_ENABLE |
| printk(DSMS_DEBUG_TAG "DSMS not initialized yet."); |
| #endif //DSMS_DEBUG_ENABLE |
| ret = -EACCES; |
| goto exit_send; |
| } |
| |
| address = __builtin_return_address(CALLER_FRAME); |
| ret = dsms_verify_access(address); |
| if (ret != DSMS_SUCCESS) |
| goto exit_send; |
| |
| ret = dsms_send_allowed_message(feature_code, detail, value); |
| |
| exit_send: |
| return ret; |
| } |