| /* |
| Copyright 2017 TeamWin |
| This file is part of TWRP/TeamWin Recovery Project. |
| |
| TWRP is free software: you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| TWRP is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with TWRP. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <sys/mount.h> |
| #include <sys/time.h> |
| #include <dirent.h> |
| #include <fnmatch.h> |
| |
| #include <signal.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| |
| #include <fstream> |
| #include <string> |
| #include <vector> |
| #include <sstream> |
| |
| #ifdef _USING_SHORT_SERVICE_NAMES |
| #include <map> |
| #endif |
| |
| #include "../../partitions.hpp" |
| #include "../../twrp-functions.hpp" |
| |
| using namespace std; |
| |
| extern "C" { |
| #include <cutils/properties.h> |
| } |
| |
| #include "vold_decrypt.h" |
| |
| namespace { |
| |
| /* Timeouts as defined by ServiceManager */ |
| |
| /* The maximum amount of time to wait for a service to start or stop, |
| * in micro-seconds (really an approximation) */ |
| #define SLEEP_MAX_USEC 2000000 /* 2 seconds */ |
| /* The minimal sleeping interval between checking for the service's state |
| * when looping for SLEEP_MAX_USEC */ |
| #define SLEEP_MIN_USEC 200000 /* 200 msec */ |
| |
| |
| /* vold response codes defined in ResponseCode.h */ |
| // 200 series - Requested action has been successfully completed |
| #define COMMAND_OKAY 200 |
| #define PASSWORD_TYPE_RESULT 213 |
| |
| |
| #define LOGINFO(...) do { printf(__VA_ARGS__); if (fp_kmsg) { fprintf(fp_kmsg, "[VOLD_DECRYPT]I:" __VA_ARGS__); fflush(fp_kmsg); } } while (0) |
| #define LOGKMSG(...) do { if (fp_kmsg) { fprintf(fp_kmsg, "[VOLD_DECRYPT]K:" __VA_ARGS__); fflush(fp_kmsg); } } while (0) |
| #define LOGERROR(...) do { printf(__VA_ARGS__); if (fp_kmsg) { fprintf(fp_kmsg, "[VOLD_DECRYPT]E:" __VA_ARGS__); fflush(fp_kmsg); } } while (0) |
| |
| FILE *fp_kmsg = NULL; |
| |
| |
| /* Debugging Functions */ |
| #ifdef TW_CRYPTO_SYSTEM_VOLD_DEBUG |
| |
| #ifndef VD_STRACE_BIN |
| #define VD_STRACE_BIN "/sbin/strace" |
| #endif |
| |
| bool has_strace = false; |
| pid_t pid_strace = 0; |
| |
| void Strace_init_Start(void) { |
| has_strace = TWFunc::Path_Exists(VD_STRACE_BIN); |
| if (!has_strace) { |
| LOGINFO("strace binary (%s) not found, disabling strace in vold_decrypt!\n", VD_STRACE_BIN); |
| return; |
| } |
| |
| pid_t pid; |
| switch(pid = fork()) |
| { |
| case -1: |
| LOGKMSG("forking strace_init failed: %d (%s)!\n", errno, strerror(errno)); |
| return; |
| case 0: // child |
| execl(VD_STRACE_BIN, "strace", "-q", "-tt", "-ff", "-v", "-y", "-s", "1000", "-o", "/tmp/strace_init.log", "-p", "1" , NULL); |
| LOGKMSG("strace_init fork failed: %d (%s)!\n", errno, strerror(errno)); |
| exit(-1); |
| default: |
| LOGKMSG("Starting strace_init (pid=%d)\n", pid); |
| pid_strace = pid; |
| return; |
| } |
| } |
| |
| void Strace_init_Stop(void) { |
| if (pid_strace > 0) { |
| LOGKMSG("Stopping strace_init (pid=%d)\n", pid_strace); |
| int timeout; |
| int status; |
| pid_t retpid = waitpid(pid_strace, &status, WNOHANG); |
| |
| kill(pid_strace, SIGTERM); |
| for (timeout = 5; retpid == 0 && timeout; --timeout) { |
| sleep(1); |
| retpid = waitpid(pid_strace, &status, WNOHANG); |
| } |
| if (retpid) |
| LOGKMSG("strace_init terminated successfully\n"); |
| else { |
| // SIGTERM didn't work, kill it instead |
| kill(pid_strace, SIGKILL); |
| for (timeout = 5; retpid == 0 && timeout; --timeout) { |
| sleep(1); |
| retpid = waitpid(pid_strace, &status, WNOHANG); |
| } |
| if (retpid) |
| LOGKMSG("strace_init killed successfully\n"); |
| else |
| LOGKMSG("strace_init took too long to kill, may be a zombie process\n"); |
| } |
| } |
| } |
| #endif // TW_CRYPTO_SYSTEM_VOLD_DEBUG |
| |
| |
| /* Convert a binary key of specified length into an ascii hex string equivalent, |
| * without the leading 0x and with null termination |
| * |
| * Original code from cryptfs.c |
| */ |
| string convert_key_to_hex_ascii(const string& master_key) { |
| size_t i; |
| unsigned char nibble; |
| string master_key_ascii = ""; |
| |
| for (i = 0; i < master_key.size(); ++i) { |
| nibble = (master_key[i] >> 4) & 0xf; |
| nibble += nibble > 9 ? 0x57 : 0x30; |
| master_key_ascii += nibble; |
| |
| nibble = master_key[i] & 0xf; |
| nibble += nibble > 9 ? 0x57 : 0x30; |
| master_key_ascii += nibble; |
| } |
| |
| return master_key_ascii; |
| } |
| |
| /* Helper Functions */ |
| #define PATH_EXISTS(path) (access(path, F_OK) >= 0) |
| |
| int vrename(const string& oldname, const string& newname, bool verbose = false) { |
| const char *old_name = oldname.c_str(); |
| const char *new_name = newname.c_str(); |
| |
| if (!PATH_EXISTS(old_name)) |
| return 0; |
| |
| if (rename(old_name, new_name) < 0) { |
| LOGERROR("Moving %s to %s failed: %d (%s)\n", old_name, new_name, errno, strerror(errno)); |
| return -1; |
| } else if (verbose) |
| LOGINFO("Renamed %s to %s\n", old_name, new_name); |
| else |
| LOGKMSG("Renamed %s to %s\n", old_name, new_name); |
| return 0; |
| } |
| |
| int vsymlink(const string& oldname, const string& newname, bool verbose = false) { |
| const char *old_name = oldname.c_str(); |
| const char *new_name = newname.c_str(); |
| |
| if (!PATH_EXISTS(old_name)) |
| return 0; |
| |
| if (symlink(old_name, new_name) < 0) { |
| LOGERROR("Symlink %s -> %s failed: %d (%s)\n", new_name, old_name, errno, strerror(errno)); |
| return -1; |
| } else if (verbose) |
| LOGINFO("Symlinked %s -> %s\n", new_name, old_name); |
| else |
| LOGKMSG("Symlinked %s -> %s\n", new_name, old_name); |
| return 0; |
| } |
| |
| |
| /* Properties and Services Functions */ |
| string Wait_For_Property(const string& property_name, int utimeout = SLEEP_MAX_USEC, const string& expected_value = "not_empty") { |
| char prop_value[PROPERTY_VALUE_MAX]; |
| |
| if (expected_value == "not_empty") { |
| while (utimeout > 0) { |
| property_get(property_name.c_str(), prop_value, "error"); |
| if (strcmp(prop_value, "error") != 0) |
| break; |
| LOGKMSG("waiting for %s to get set\n", property_name.c_str()); |
| utimeout -= SLEEP_MIN_USEC; |
| usleep(SLEEP_MIN_USEC);; |
| } |
| } |
| else { |
| while (utimeout > 0) { |
| property_get(property_name.c_str(), prop_value, "error"); |
| if (strcmp(prop_value, expected_value.c_str()) == 0) |
| break; |
| LOGKMSG("waiting for %s to change from '%s' to '%s'\n", property_name.c_str(), prop_value, expected_value.c_str()); |
| utimeout -= SLEEP_MIN_USEC; |
| usleep(SLEEP_MIN_USEC);; |
| } |
| } |
| property_get(property_name.c_str(), prop_value, "error"); |
| |
| return prop_value; |
| } |
| |
| string Get_Service_State(const string& initrc_svc) { |
| char prop_value[PROPERTY_VALUE_MAX]; |
| string init_svc = "init.svc." + initrc_svc; |
| property_get(init_svc.c_str(), prop_value, "error"); |
| return prop_value; |
| } |
| |
| bool Service_Exists(const string& initrc_svc) { |
| return (Get_Service_State(initrc_svc) != "error"); |
| } |
| |
| bool Is_Service_Running(const string& initrc_svc) { |
| return (Get_Service_State(initrc_svc) == "running"); |
| } |
| |
| bool Is_Service_Stopped(const string& initrc_svc) { |
| return (Get_Service_State(initrc_svc) == "stopped"); |
| } |
| |
| bool Start_Service(const string& initrc_svc, int utimeout = SLEEP_MAX_USEC) { |
| string res = "error"; |
| string init_svc = "init.svc." + initrc_svc; |
| |
| property_set("ctl.start", initrc_svc.c_str()); |
| |
| res = Wait_For_Property(init_svc, utimeout, "running"); |
| |
| LOGINFO("Start service %s: %s.\n", initrc_svc.c_str(), res.c_str()); |
| |
| return (res == "running"); |
| } |
| |
| bool Stop_Service(const string& initrc_svc, int utimeout = SLEEP_MAX_USEC) { |
| string res = "error"; |
| |
| if (Service_Exists(initrc_svc)) { |
| string init_svc = "init.svc." + initrc_svc; |
| property_set("ctl.stop", initrc_svc.c_str()); |
| res = Wait_For_Property(init_svc, utimeout, "stopped"); |
| LOGINFO("Stop service %s: %s.\n", initrc_svc.c_str(), res.c_str()); |
| } |
| |
| return (res == "stopped"); |
| } |
| |
| |
| /* Vendor, Firmware and fstab symlink Functions */ |
| bool is_Vendor_Mounted(void) { |
| static int is_mounted = -1; |
| if (is_mounted < 0) |
| is_mounted = PartitionManager.Is_Mounted_By_Path("/vendor") ? 1 : 0; |
| return is_mounted; |
| } |
| |
| bool is_Firmware_Mounted(void) { |
| static int is_mounted = -1; |
| if (is_mounted < 0) |
| is_mounted = PartitionManager.Is_Mounted_By_Path("/firmware") ? 1 : 0; |
| return is_mounted; |
| } |
| |
| bool will_VendorBin_Be_Symlinked(void) { |
| return (!is_Vendor_Mounted() && TWFunc::Path_Exists("/system/vendor")); |
| } |
| |
| bool Symlink_Vendor_Folder(void) { |
| bool is_vendor_symlinked = false; |
| |
| if (is_Vendor_Mounted()) { |
| LOGINFO("vendor partition mounted, skipping /vendor substitution\n"); |
| } |
| else if (TWFunc::Path_Exists("/system/vendor")) { |
| LOGINFO("Symlinking vendor folder...\n"); |
| if (!TWFunc::Path_Exists("/vendor") || vrename("/vendor", "/vendor-orig") == 0) { |
| TWFunc::Recursive_Mkdir("/vendor/firmware/keymaster"); |
| vsymlink("/system/vendor/lib64", "/vendor/lib64", true); |
| vsymlink("/system/vendor/lib", "/vendor/lib", true); |
| vsymlink("/system/vendor/bin", "/vendor/bin", true); |
| is_vendor_symlinked = true; |
| property_set("vold_decrypt.symlinked_vendor", "1"); |
| } |
| } |
| return is_vendor_symlinked; |
| } |
| |
| void Restore_Vendor_Folder(void) { |
| property_set("vold_decrypt.symlinked_vendor", "0"); |
| TWFunc::removeDir("/vendor", false); |
| vrename("/vendor-orig", "/vendor"); |
| } |
| |
| bool Symlink_Firmware_Folder(void) { |
| bool is_firmware_symlinked = false; |
| |
| if (is_Firmware_Mounted()) { |
| LOGINFO("firmware partition mounted, skipping /firmware substitution\n"); |
| } |
| else { |
| LOGINFO("Symlinking firmware folder...\n"); |
| if (!TWFunc::Path_Exists("/firmware") || vrename("/firmware", "/firmware-orig") == 0) { |
| TWFunc::Recursive_Mkdir("/firmware/image"); |
| is_firmware_symlinked = true; |
| property_set("vold_decrypt.symlinked_firmware", "1"); |
| } |
| } |
| return is_firmware_symlinked; |
| } |
| |
| void Restore_Firmware_Folder(void) { |
| property_set("vold_decrypt.symlinked_firmware", "0"); |
| TWFunc::removeDir("/firmware", false); |
| vrename("/firmware-orig", "/firmware"); |
| } |
| |
| int Find_Firmware_Files(const string& Path, vector<string> *FileList) { |
| int ret; |
| DIR* d; |
| struct dirent* de; |
| string FileName; |
| |
| d = opendir(Path.c_str()); |
| if (d == NULL) { |
| closedir(d); |
| return -1; |
| } |
| while ((de = readdir(d)) != NULL) { |
| if (de->d_type == DT_DIR) { |
| if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) |
| continue; |
| FileName = Path + "/" + de->d_name; |
| ret = Find_Firmware_Files(FileName, FileList); |
| if (ret < 0) |
| return -1; |
| } else if (de->d_type == DT_REG) { |
| if (fnmatch("keymaste*.*", de->d_name, 0) == 0 || fnmatch("cmnlib.*", de->d_name, 0) == 0) { |
| FileName = Path + "/" + de->d_name; |
| FileList->push_back(FileName); |
| } |
| } |
| } |
| closedir(d); |
| return 0; |
| } |
| |
| void Symlink_Firmware_Files(bool is_vendor_symlinked, bool is_firmware_symlinked) { |
| if (!is_vendor_symlinked && !is_firmware_symlinked) |
| return; |
| |
| LOGINFO("Symlinking firmware files...\n"); |
| |
| vector<string> FirmwareFiles; |
| Find_Firmware_Files("/system", &FirmwareFiles); |
| |
| for (size_t i = 0; i < FirmwareFiles.size(); ++i) { |
| string base_name = TWFunc::Get_Filename(FirmwareFiles[i]); |
| |
| if (is_firmware_symlinked) |
| vsymlink(FirmwareFiles[i], "/firmware/image/" + base_name); |
| |
| if (is_vendor_symlinked) { |
| vsymlink(FirmwareFiles[i], "/vendor/firmware/" + base_name); |
| vsymlink(FirmwareFiles[i], "/vendor/firmware/keymaster/" + base_name); |
| } |
| } |
| LOGINFO("%d file(s) symlinked.\n", (int)FirmwareFiles.size()); |
| } |
| |
| // Android 8.0 fs_mgr checks for "/sbin/recovery", in which case it will |
| // use /etc/recovery.fstab -> symlink it temporarily. Reference: |
| // https://android.googlesource.com/platform/system/core/+/android-8.0.0_r17/fs_mgr/fs_mgr_fstab.cpp#693 |
| bool Symlink_Recovery_Fstab(void) { |
| bool is_fstab_symlinked = false; |
| |
| if (vrename("/etc/recovery.fstab", "/etc/recovery-fstab-orig") == 0) { |
| is_fstab_symlinked = true; |
| |
| // now attempt to symlink to /fstab.{ro.hardware}, but even if that |
| // fails, keep TWRP's fstab hidden since it cannot be parsed by fs_mgr |
| char prop_value[PROPERTY_VALUE_MAX]; |
| property_get("ro.hardware", prop_value, "error"); |
| if (strcmp(prop_value, "error")) { |
| string fstab_device = "/fstab."; fstab_device += prop_value; |
| vsymlink(fstab_device, "/etc/recovery.fstab"); |
| } |
| } |
| return is_fstab_symlinked; |
| } |
| |
| void Restore_Recovery_Fstab(void) { |
| unlink("/etc/recovery.fstab"); |
| vrename("/etc/recovery-fstab-orig", "/etc/recovery.fstab"); |
| } |
| |
| |
| /* Additional Services Functions */ |
| #ifdef TW_CRYPTO_SYSTEM_VOLD_SERVICES |
| typedef struct { |
| string Service_Name; |
| string Service_Path; |
| string Service_Binary; |
| |
| string VOLD_Service_Name; |
| string TWRP_Service_Name; |
| bool is_running; |
| bool resume; |
| bool bin_exists; |
| bool svc_exists; |
| } AdditionalService; |
| |
| typedef struct { |
| string Service_Name; |
| string Service_Path; |
| string Service_Binary; |
| } RC_Service; |
| |
| // expand_props() courtesy of platform_system_core_init_util.cpp |
| bool expand_props(const std::string& src, std::string* dst) { |
| const char* src_ptr = src.c_str(); |
| |
| if (!dst) { |
| return false; |
| } |
| |
| /* - variables can either be $x.y or ${x.y}, in case they are only part |
| * of the string. |
| * - will accept $$ as a literal $. |
| * - no nested property expansion, i.e. ${foo.${bar}} is not supported, |
| * bad things will happen |
| * - ${x.y:-default} will return default value if property empty. |
| */ |
| while (*src_ptr) { |
| const char* c; |
| |
| c = strchr(src_ptr, '$'); |
| if (!c) { |
| dst->append(src_ptr); |
| return true; |
| } |
| |
| dst->append(src_ptr, c); |
| c++; |
| |
| if (*c == '$') { |
| dst->push_back(*(c++)); |
| src_ptr = c; |
| continue; |
| } else if (*c == '\0') { |
| return true; |
| } |
| |
| std::string prop_name; |
| std::string def_val; |
| if (*c == '{') { |
| c++; |
| const char* end = strchr(c, '}'); |
| if (!end) { |
| // failed to find closing brace, abort. |
| return false; |
| } |
| prop_name = std::string(c, end); |
| c = end + 1; |
| size_t def = prop_name.find(":-"); |
| if (def < prop_name.size()) { |
| def_val = prop_name.substr(def + 2); |
| prop_name = prop_name.substr(0, def); |
| } |
| } else { |
| prop_name = c; |
| c += prop_name.size(); |
| } |
| |
| if (prop_name.empty()) { |
| return false; |
| } |
| |
| char prop_value[PROPERTY_VALUE_MAX]; |
| property_get(prop_name.c_str(), prop_value, ""); |
| std::string prop_val = prop_value; |
| if (prop_val.empty()) { |
| if (def_val.empty()) { |
| return false; |
| } |
| prop_val = def_val; |
| } |
| |
| dst->append(prop_val); |
| src_ptr = c; |
| } |
| |
| return true; |
| } |
| |
| string GetArgument(const string& line, size_t argument_number, bool expand_properties) { |
| size_t beg; |
| size_t end; |
| string argument; |
| |
| beg = line.find_first_not_of(" \t\r"); |
| if (beg == string::npos) |
| return ""; |
| |
| for (size_t i = 0; i < argument_number; ++i) { |
| end = line.find_first_of(" \t\r", beg); |
| if (end == string::npos) |
| return ""; |
| |
| beg = line.find_first_not_of(" \t\r", end); |
| if (beg == string::npos) |
| return ""; |
| } |
| |
| end = line.find_first_of(" \t\r", beg); |
| if (end == string::npos) |
| argument = line.substr(beg); |
| else |
| argument = line.substr(beg, end - beg); // exclude trailing whitespace |
| |
| if (expand_properties) { |
| string expanded_property_argument; |
| if (expand_props(argument, &expanded_property_argument)) |
| return expanded_property_argument; |
| else |
| return ""; |
| } else { |
| return argument; |
| } |
| } |
| |
| // Very simplified .rc parser to get services |
| void Parse_RC_File(const string& rc_file, vector<RC_Service>& RC_Services) { |
| ifstream file; |
| |
| file.open(rc_file.c_str(), ios::in); |
| if (!file.is_open()) |
| return; |
| |
| size_t beg; // left trim |
| size_t end; // right trim |
| bool continuation = false; // backslash continuation |
| string line; // line |
| string real_line; // trimmed line with backslash continuation removal |
| vector<string> imports; // file names of imports (we don't want to recursively do while the file is open) |
| |
| while (getline(file, line)) { |
| beg = line.find_first_not_of(" \t\r"); |
| end = line.find_last_not_of(" \t\r"); |
| if (end == string::npos) |
| end = line.length(); |
| |
| if (beg == string::npos) { |
| if (continuation) |
| continuation = false; |
| else |
| continue; |
| } else if (line[end] == '\\') { |
| continuation = true; |
| real_line += line.substr(beg, end - beg); // excluding backslash |
| continue; |
| } else if (continuation) { |
| continuation = false; |
| real_line += line.substr(beg, end - beg + 1); |
| } else { |
| real_line = line.substr(beg, end - beg + 1); |
| } |
| |
| if (GetArgument(real_line, 0, false) == "import") { |
| // handle: import <file> |
| string file_name = GetArgument(real_line, 1, true); |
| if (file_name.empty()) { |
| // INVALID IMPORT |
| } else |
| imports.push_back(file_name); |
| } else if (GetArgument(real_line, 0, false) == "service") { |
| // handle: service <name> <path> |
| RC_Service svc; |
| |
| svc.Service_Name = GetArgument(real_line, 1, false); |
| svc.Service_Path = GetArgument(real_line, 2, true); |
| |
| if (svc.Service_Name.empty() || svc.Service_Path.empty()) { |
| // INVALID SERVICE ENTRY |
| } else { |
| beg = svc.Service_Path.find_last_of("/"); |
| if (beg == string::npos) |
| svc.Service_Binary = svc.Service_Path; |
| else |
| svc.Service_Binary = svc.Service_Path.substr(beg + 1); |
| |
| /* |
| #ifdef _USING_SHORT_SERVICE_NAMES |
| if (svc.Service_Name.length() > 16) { |
| LOGERROR("WARNING: Ignoring service %s (-> %s)\n" |
| " defined in %s is greater than 16 characters and will\n" |
| " not be able to be run by init on android-7.1 or below!\n", |
| svc.Service_Name.c_str(), svc.Service_Path.c_str(), rc_file.c_str() |
| ); |
| } |
| else |
| #endif |
| */ |
| RC_Services.push_back(svc); |
| } |
| } |
| real_line.clear(); |
| } |
| file.close(); |
| |
| for (size_t i = 0; i < imports.size(); ++i) { |
| Parse_RC_File(imports[i], RC_Services); |
| } |
| } |
| |
| vector<AdditionalService> Get_List_Of_Additional_Services(void) { |
| vector<AdditionalService> services; |
| |
| // Additional Services needed by vold_decrypt (eg qseecomd, hwservicemanager, etc) |
| vector<string> service_names = TWFunc::Split_String(TW_CRYPTO_SYSTEM_VOLD_SERVICES, " "); |
| for (size_t i = 0; i < service_names.size(); ++i) { |
| AdditionalService svc; |
| svc.Service_Name = service_names[i]; |
| svc.bin_exists = false; |
| svc.svc_exists = false; |
| services.push_back(svc); |
| |
| #ifdef _USING_SHORT_SERVICE_NAMES |
| // Fallback code for >16 character service names which |
| // allows for multiple definitions in custom .rc files |
| if (service_names[i].length() > 12) { |
| svc.Service_Name = service_names[i].substr(0, 12); // 16-4(prefix)=12 |
| svc.bin_exists = false; |
| svc.svc_exists = false; |
| services.push_back(svc); |
| } |
| #endif |
| } |
| |
| // Read list of all services defined in all .rc files |
| vector<RC_Service> RC_Services; |
| Parse_RC_File("/init.rc", RC_Services); |
| |
| |
| // Cross reference Additional Services against the .rc Services and establish |
| // availability of the binaries, otherwise disable it to avoid unnecessary |
| // delays and log spam. |
| // Also check for duplicate entries between TWRP and vold_decrypt so we can |
| // stop and restart any conflicting services. |
| for (size_t i = 0; i < RC_Services.size(); ++i) { |
| string prefix = RC_Services[i].Service_Name.substr(0, 4); |
| |
| #ifdef _USING_SHORT_SERVICE_NAMES |
| map<string,size_t> rc_indeces; |
| #endif |
| |
| if (prefix != "sys_" && prefix != "ven_") { |
| #ifdef _USING_SHORT_SERVICE_NAMES |
| if (RC_Services[i].Service_Name.length() > 12) { |
| // save this entry for potential binary name match |
| rc_indeces.insert(pair<string,size_t>(RC_Services[i].Service_Binary, i)); |
| } |
| #endif |
| continue; |
| } |
| |
| for (size_t j = 0; j < services.size(); ++j) { |
| string path = RC_Services[i].Service_Path; |
| if (prefix == "ven_" && will_VendorBin_Be_Symlinked()) { |
| path = "/system" + path; // vendor is going to get symlinked to /system/vendor |
| } |
| |
| if (RC_Services[i].Service_Name == prefix + services[j].Service_Name) { |
| services[j].svc_exists = true; |
| |
| if (!services[j].VOLD_Service_Name.empty() && TWFunc::Path_Exists(path)) { |
| // Duplicate match, log but use previous definition |
| LOGERROR("Service %s: VOLD_Service_Name already defined as %s\n", RC_Services[i].Service_Name.c_str(), services[j].VOLD_Service_Name.c_str()); |
| } |
| else if (TWFunc::Path_Exists(path)) { |
| services[j].bin_exists = true; |
| services[j].VOLD_Service_Name = RC_Services[i].Service_Name; // prefix + service_name |
| services[j].Service_Path = RC_Services[i].Service_Path; |
| services[j].Service_Binary = RC_Services[i].Service_Binary; |
| |
| if (Service_Exists(services[j].Service_Name)) |
| services[j].TWRP_Service_Name = services[j].Service_Name; |
| else if (Service_Exists("sbin" + services[j].Service_Name)) |
| services[j].TWRP_Service_Name = "sbin" + services[j].Service_Name; |
| else |
| services[j].TWRP_Service_Name.clear(); |
| |
| #ifdef _USING_SHORT_SERVICE_NAMES |
| if (services[j].TWRP_Service_Name.empty()) { |
| // Try matching Service_Binary (due to 16 character service_name limit in 7.1 and below) |
| map<string,size_t>::iterator it = rc_indeces.find(services[j].Service_Binary); |
| if (it != rc_indeces.end()) { |
| services[j].TWRP_Service_Name = RC_Services[it->second].Service_Name; |
| } |
| } |
| #endif |
| } |
| break; |
| } |
| } |
| } |
| |
| LOGINFO("List of additional services for vold_decrypt:\n"); |
| for (size_t i = 0; i < services.size(); ++i) { |
| if (!services[i].svc_exists) { |
| LOGINFO(" %s: Disabled due to lack of .rc service entry\n", services[i].Service_Name.c_str()); |
| } else if (services[i].bin_exists) { |
| if (services[i].TWRP_Service_Name.empty()) { |
| LOGINFO(" %s: Enabled as %s -> %s\n", |
| services[i].Service_Name.c_str(), |
| services[i].VOLD_Service_Name.c_str(), services[i].Service_Path.c_str() |
| ); |
| } else { |
| LOGINFO(" %s: Enabled as %s -> %s (temporarily replacing TWRP's %s service)\n", |
| services[i].Service_Name.c_str(), |
| services[i].VOLD_Service_Name.c_str(), services[i].Service_Path.c_str(), |
| services[i].TWRP_Service_Name.c_str() |
| ); |
| } |
| } |
| else { |
| LOGINFO(" %s: Disabled due to lack of matching binary\n", services[i].Service_Name.c_str()); |
| } |
| } |
| return services; |
| } |
| #endif |
| |
| |
| /* Misc Functions */ |
| void Set_Needed_Properties(void) { |
| // vold won't start without ro.storage_structure on Kitkat |
| string sdkverstr = TWFunc::System_Property_Get("ro.build.version.sdk"); |
| int sdkver = 20; |
| if (!sdkverstr.empty()) { |
| sdkver = atoi(sdkverstr.c_str()); |
| } |
| if (sdkver <= 19) { |
| string ro_storage_structure = TWFunc::System_Property_Get("ro.storage_structure"); |
| if (!ro_storage_structure.empty()) |
| property_set("ro.storage_structure", ro_storage_structure.c_str()); |
| } |
| } |
| |
| |
| /* vdc Functions */ |
| typedef struct { |
| string Output; // Entire line excluding \n |
| int ResponseCode; // ResponseCode.h (int) |
| int Sequence; // Sequence (int) |
| int Message; // Message (string) but we're only interested in int |
| } vdc_ReturnValues; |
| |
| int Exec_vdc_cryptfs(const string& command, const string& argument, vdc_ReturnValues* vdcResult) { |
| pid_t pid; |
| int status; |
| int pipe_fd[2][2]; |
| |
| vdcResult->Output.clear(); |
| vdcResult->ResponseCode = vdcResult->Sequence = vdcResult->Message = -1; |
| |
| for (int i = 0; i < 2; ++i) { |
| if (pipe(pipe_fd[i])) { |
| LOGERROR("exec_vdc_cryptfs: pipe() error!\n"); |
| return -1; |
| } |
| } |
| |
| const char *cmd[] = { "/system/bin/vdc", "cryptfs" }; |
| const char *env[] = { "LD_LIBRARY_PATH=/system/lib64:/system/lib", NULL }; |
| |
| #ifdef TW_CRYPTO_SYSTEM_VOLD_DEBUG |
| string log_name = "/tmp/strace_vdc_" + command; |
| #endif |
| |
| switch(pid = fork()) |
| { |
| case -1: |
| LOGERROR("exec_vdc_cryptfs: fork failed: %d (%s)!\n", errno, strerror(errno)); |
| return -1; |
| |
| case 0: // child |
| fflush(stdout); fflush(stderr); |
| for (int i = 0; i < 2; ++i) { |
| close(pipe_fd[i][0]); |
| dup2(pipe_fd[i][1], ((i == 0) ? STDOUT_FILENO : STDERR_FILENO)); |
| close(pipe_fd[i][1]); |
| } |
| |
| #ifdef TW_CRYPTO_SYSTEM_VOLD_DEBUG |
| if (has_strace) { |
| if (argument.empty()) |
| execl(VD_STRACE_BIN, "strace", "-q", "-tt", "-ff", "-v", "-y", "-s", "1000", "-o", log_name.c_str(), |
| "-E", env[0], cmd[0], cmd[1], command.c_str(), NULL); |
| else |
| execl(VD_STRACE_BIN, "strace", "-q", "-tt", "-ff", "-v", "-y", "-s", "1000", "-o", log_name.c_str(), |
| "-E", env[0], cmd[0], cmd[1], command.c_str(), argument.c_str(), NULL); |
| } else |
| #endif |
| if (argument.empty()) |
| execle(cmd[0], cmd[0], cmd[1], command.c_str(), NULL, env); |
| else |
| execle(cmd[0], cmd[0], cmd[1], command.c_str(), argument.c_str(), NULL, env); |
| _exit(127); |
| break; |
| |
| default: |
| { |
| int timeout = 30*100; |
| |
| for (int i = 0; i < 2; ++i) { |
| close(pipe_fd[i][1]); |
| |
| // Non-blocking read loop with timeout |
| int flags = fcntl(pipe_fd[i][0], F_GETFL, 0); |
| fcntl(pipe_fd[i][0], F_SETFL, flags | O_NONBLOCK); |
| } |
| |
| char buffer[128]; |
| ssize_t count; |
| string strout[2]; |
| pid_t retpid = waitpid(pid, &status, WNOHANG); |
| while (true) { |
| for (int i = 0; i < 2; ++i) { |
| count = read(pipe_fd[i][0], buffer, sizeof(buffer)); |
| if (count == -1) { |
| if (errno == EINTR) |
| continue; |
| else if (errno != EAGAIN) |
| LOGERROR("exec_vdc_cryptfs: read() error %d (%s)\n!", errno, strerror(errno)); |
| } else if (count > 0) { |
| strout[i].append(buffer, count); |
| } |
| } |
| |
| retpid = waitpid(pid, &status, WNOHANG); |
| if (retpid == 0 && --timeout) |
| usleep(10000); |
| else |
| break; |
| }; |
| |
| for (int i = 0; i < 2; ++i) { |
| close(pipe_fd[i][0]); |
| } |
| |
| if (!strout[0].empty()) { |
| sscanf(strout[0].c_str(), "%d %d %d", &vdcResult->ResponseCode, &vdcResult->Sequence, &vdcResult->Message); |
| vdcResult->Output = "I:" + strout[0]; |
| } |
| if (!strout[1].empty()) { |
| vdcResult->Output += "E:" + strout[1]; |
| } |
| std::replace(vdcResult->Output.begin(), vdcResult->Output.end(), '\n', '|'); |
| |
| if (!vdcResult->Output.empty() && vdcResult->Output[vdcResult->Output.length() - 1] != '|') |
| vdcResult->Output += "|"; |
| vdcResult->Output += "RC=" + TWFunc::to_string(WEXITSTATUS(status)); |
| |
| // Error handling |
| if (retpid == 0 && timeout == 0) { |
| LOGERROR("exec_vdc_cryptfs: took too long, killing process\n"); |
| kill(pid, SIGKILL); |
| for (timeout = 5; retpid == 0 && timeout; --timeout) { |
| sleep(1); |
| retpid = waitpid(pid, &status, WNOHANG); |
| } |
| if (retpid) |
| LOGINFO("exec_vdc_cryptfs: process killed successfully\n"); |
| else |
| LOGERROR("exec_vdc_cryptfs: process took too long to kill, may be a zombie process\n"); |
| return VD_ERR_VOLD_OPERATION_TIMEDOUT; |
| } else if (retpid > 0) { |
| if (WIFSIGNALED(status)) { |
| LOGERROR("exec_vdc_cryptfs: process ended with signal: %d\n", WTERMSIG(status)); // Seg fault or some other non-graceful termination |
| return -1; |
| } |
| } else if (retpid < 0) { // no PID returned |
| if (errno == ECHILD) |
| LOGINFO("exec_vdc_cryptfs: no child process exist\n"); |
| else { |
| LOGERROR("exec_vdc_cryptfs: Unexpected error %d (%s)\n", errno, strerror(errno)); |
| return -1; |
| } |
| } |
| return 0; |
| } |
| } |
| } |
| |
| int Run_vdc(const string& Password) { |
| int res; |
| struct timeval t1, t2; |
| vdc_ReturnValues vdcResult; |
| |
| LOGINFO("About to run vdc...\n"); |
| |
| // Wait for vold connection |
| gettimeofday(&t1, NULL); |
| t2 = t1; |
| while ((t2.tv_sec - t1.tv_sec) < 5) { |
| // cryptfs getpwtype returns: R1=213(PasswordTypeResult) R2=? R3="password", "pattern", "pin", "default" |
| res = Exec_vdc_cryptfs("getpwtype", "", &vdcResult); |
| if (vdcResult.ResponseCode == PASSWORD_TYPE_RESULT) { |
| res = 0; |
| break; |
| } |
| LOGINFO("Retrying connection to vold (Reason: %s)\n", vdcResult.Output.c_str()); |
| usleep(SLEEP_MIN_USEC); // vdc usually usleep(10000), but that causes too many unnecessary attempts |
| gettimeofday(&t2, NULL); |
| } |
| |
| if (res == 0 && (t2.tv_sec - t1.tv_sec) < 5) |
| LOGINFO("Connected to vold: %s\n", vdcResult.Output.c_str()); |
| else if (res == VD_ERR_VOLD_OPERATION_TIMEDOUT) |
| return VD_ERR_VOLD_OPERATION_TIMEDOUT; // should never happen for getpwtype |
| else if (res) |
| return VD_ERR_FORK_EXECL_ERROR; |
| else if (vdcResult.ResponseCode != -1) |
| return VD_ERR_VOLD_UNEXPECTED_RESPONSE; |
| else |
| return VD_ERR_VDC_FAILED_TO_CONNECT; |
| |
| |
| // Input password from GUI, or default password |
| res = Exec_vdc_cryptfs("checkpw", Password, &vdcResult); |
| if (res == VD_ERR_VOLD_OPERATION_TIMEDOUT) |
| return VD_ERR_VOLD_OPERATION_TIMEDOUT; |
| else if (res) |
| return VD_ERR_FORK_EXECL_ERROR; |
| |
| LOGINFO("vdc cryptfs result (passwd): %s\n", vdcResult.Output.c_str()); |
| /* |
| if (res == 0 && vdcResult.ResponseCode != COMMAND_OKAY) |
| return VD_ERR_VOLD_UNEXPECTED_RESPONSE; |
| */ |
| |
| if (vdcResult.Message != 0) { |
| // try falling back to Lollipop hex passwords |
| string hexPassword = convert_key_to_hex_ascii(Password); |
| res = Exec_vdc_cryptfs("checkpw", hexPassword, &vdcResult); |
| if (res == VD_ERR_VOLD_OPERATION_TIMEDOUT) |
| return VD_ERR_VOLD_OPERATION_TIMEDOUT; |
| else if (res) |
| return VD_ERR_FORK_EXECL_ERROR; |
| |
| LOGINFO("vdc cryptfs result (hex_pw): %s\n", vdcResult.Output.c_str()); |
| /* |
| if (res == 0 && vdcResult.ResponseCode != COMMAND_OKAY) |
| return VD_ERR_VOLD_UNEXPECTED_RESPONSE; |
| */ |
| } |
| |
| // vdc's return value is dependant upon source origin, it will either |
| // return 0 or ResponseCode, so disregard and focus on decryption instead |
| if (vdcResult.Message == 0) { |
| // Decryption successful wait for crypto blk dev |
| Wait_For_Property("ro.crypto.fs_crypto_blkdev"); |
| res = VD_SUCCESS; |
| } else if (vdcResult.ResponseCode != COMMAND_OKAY) { |
| res = VD_ERR_VOLD_UNEXPECTED_RESPONSE; |
| } else { |
| res = VD_ERR_DECRYPTION_FAILED; |
| } |
| |
| return res; |
| } |
| |
| int Vold_Decrypt_Core(const string& Password) { |
| int res; |
| bool is_vendor_symlinked = false; |
| bool is_firmware_symlinked = false; |
| bool is_fstab_symlinked = false; |
| bool is_vold_running = false; |
| |
| if (Password.empty()) { |
| LOGINFO("vold_decrypt: password is empty!\n"); |
| return VD_ERR_PASSWORD_EMPTY; |
| } |
| |
| // Mount system and check for vold and vdc |
| if (!PartitionManager.Mount_By_Path("/system", true)) { |
| return VD_ERR_UNABLE_TO_MOUNT_SYSTEM; |
| } else if (!TWFunc::Path_Exists("/system/bin/vold")) { |
| LOGINFO("ERROR: /system/bin/vold not found, aborting.\n"); |
| return VD_ERR_MISSING_VOLD; |
| } else if (!TWFunc::Path_Exists("/system/bin/vdc")) { |
| LOGINFO("ERROR: /system/bin/vdc not found, aborting.\n"); |
| return VD_ERR_MISSING_VDC; |
| } |
| |
| fp_kmsg = fopen("/dev/kmsg", "a"); |
| |
| LOGINFO("TW_CRYPTO_USE_SYSTEM_VOLD := true\n"); |
| |
| // just cache the result to avoid unneeded duplicates in recovery.log |
| LOGINFO("Checking existence of vendor and firmware partitions...\n"); |
| is_Vendor_Mounted(); |
| is_Firmware_Mounted(); |
| |
| LOGINFO("Attempting to use system's vold for decryption...\n"); |
| |
| #ifdef TW_CRYPTO_SYSTEM_VOLD_DEBUG |
| Strace_init_Start(); |
| #endif |
| |
| #ifdef TW_CRYPTO_SYSTEM_VOLD_SERVICES |
| vector<AdditionalService> Services = Get_List_Of_Additional_Services(); |
| |
| // Check if TWRP is running any of the services |
| for (size_t i = 0; i < Services.size(); ++i) { |
| if (!Services[i].TWRP_Service_Name.empty() && !Is_Service_Stopped(Services[i].TWRP_Service_Name)) { |
| Services[i].resume = true; |
| Stop_Service(Services[i].TWRP_Service_Name); |
| } else |
| Services[i].resume = false; |
| } |
| #endif |
| |
| LOGINFO("Setting up folders and permissions...\n"); |
| is_fstab_symlinked = Symlink_Recovery_Fstab(); |
| is_vendor_symlinked = Symlink_Vendor_Folder(); |
| is_firmware_symlinked = Symlink_Firmware_Folder(); |
| Symlink_Firmware_Files(is_vendor_symlinked, is_firmware_symlinked); |
| |
| Set_Needed_Properties(); |
| |
| // Start services needed for vold decrypt |
| LOGINFO("Starting services...\n"); |
| #ifdef TW_CRYPTO_SYSTEM_VOLD_SERVICES |
| for (size_t i = 0; i < Services.size(); ++i) { |
| if (Services[i].bin_exists) |
| Services[i].is_running = Start_Service(Services[i].VOLD_Service_Name); |
| } |
| #endif |
| is_vold_running = Start_Service("sys_vold"); |
| |
| if (is_vold_running) { |
| #ifdef TW_CRYPTO_SYSTEM_VOLD_SERVICES |
| for (size_t i = 0; i < Services.size(); ++i) { |
| if (Services[i].bin_exists && !Is_Service_Running(Services[i].VOLD_Service_Name) && Services[i].resume) { |
| // if system_service has died restart the twrp_service |
| LOGINFO("%s is not running, resuming %s!\n", Services[i].VOLD_Service_Name.c_str(), Services[i].TWRP_Service_Name.c_str()); |
| Start_Service(Services[i].TWRP_Service_Name); |
| } |
| } |
| #endif |
| res = Run_vdc(Password); |
| |
| if (res != 0) { |
| LOGINFO("Decryption failed\n"); |
| } |
| } else { |
| LOGINFO("Failed to start vold\n"); |
| res = VD_ERR_VOLD_FAILED_TO_START; |
| } |
| |
| // Stop services needed for vold decrypt so /system can be unmounted |
| LOGINFO("Stopping services...\n"); |
| Stop_Service("sys_vold"); |
| #ifdef TW_CRYPTO_SYSTEM_VOLD_SERVICES |
| for (size_t i = 0; i < Services.size(); ++i) { |
| if (!Is_Service_Running(Services[i].VOLD_Service_Name) && Services[i].resume) |
| Stop_Service(Services[i].TWRP_Service_Name); |
| else if (Services[i].bin_exists) |
| Stop_Service(Services[i].VOLD_Service_Name); |
| } |
| #endif |
| |
| if (is_firmware_symlinked) |
| Restore_Firmware_Folder(); |
| if (is_vendor_symlinked) |
| Restore_Vendor_Folder(); |
| if (is_fstab_symlinked) |
| Restore_Recovery_Fstab(); |
| |
| if (!PartitionManager.UnMount_By_Path("/system", true)) { |
| // PartitionManager failed to unmount /system, this should not happen, |
| // but in case it does, do a lazy unmount |
| LOGINFO("WARNING: system could not be unmounted normally!\n"); |
| umount2("/system", MNT_DETACH); |
| } |
| |
| LOGINFO("Finished.\n"); |
| |
| #ifdef TW_CRYPTO_SYSTEM_VOLD_SERVICES |
| // Restart previously running services |
| for (size_t i = 0; i < Services.size(); ++i) { |
| if (Services[i].resume) |
| Start_Service(Services[i].TWRP_Service_Name); |
| } |
| #endif |
| |
| #ifdef TW_CRYPTO_SYSTEM_VOLD_DEBUG |
| Strace_init_Stop(); |
| #endif |
| |
| // Finish up and exit |
| if (fp_kmsg) { |
| fflush(fp_kmsg); |
| fclose(fp_kmsg); |
| } |
| |
| return res; |
| } |
| |
| } // namespace |
| |
| /* |
| * Common vold Response Codes / Errors: |
| * 406 (OpFailedStorageNotFound) -> Problem reading or parsing fstab |
| * |
| */ |
| |
| /* Main function separated from core in case we want to return error info */ |
| int vold_decrypt(const string& Password) { |
| return Vold_Decrypt_Core(Password); |
| } |