运行于LocalService账户的Windows服务无法读取非LOCAL_SERVICE所有的文件
Windows服务读取配置文件失败权限问题咨询
问题描述
使用C++结合Win32 API开发了一款Windows服务程序,程序进入正式服务运行模式前,会尝试读取指定绝对路径下的配置文件,读取操作失败会导致程序直接退出。经多轮调试排查,初步怀疑故障原因与文件所有权权限配置有关,现需可行解决方案,优先提供PowerShell脚本实现的文件所有权修改方案。
已排查上下文信息
- 主程序逻辑:读取配置文件失败时直接退出,核心代码如下:
int main() { std::string cfg_file_name = config::get_config(config::comm_config_file); if (cfg.read(cfg_file_name) < 0) { events::start_log(cfg.log_path, cfg.log_spdlog_level); SPDLOG_CRITICAL("Cannot read: " + cfg_file_name); return 1; }
get_config()函数逻辑:调用Win32 API获取可执行文件所在路径(即配置文件存放路径),将其与配置文件名拼接得到绝对路径,实现代码如下:
std::string config::get_config(const std::string& config_name) { #ifdef _MSC_VER std::string s = config::get_executable_path(); // 日志启动前的调试输出,默认写入C:\Windows\System32\comm.txt std::ofstream ofs("comm.txt"); ofs << "GetModuleFileName: " << s << std::endl; TCHAR buf[MAX_PATH]; GetCurrentDirectory(MAX_PATH, buf); ofs << "GetCurrentDirectory: " << buf << std::endl; // 将进程当前目录修改为可执行文件所在路径 if (SetCurrentDirectory(s.c_str()) == 0) { ofs << "SetCurrentDirectory failed" << std::endl; } GetCurrentDirectory(MAX_PATH, buf); ofs << "GetCurrentDirectory after set: " << buf << std::endl; s += config_name; ofs.close(); return s; #else return config_name; #endif }
- 服务创建命令:通过PowerShell脚本调用sc.exe创建服务,执行命令如下:
sc.exe create _comm_ftp_server binPath= "$install_dir\ftp_server.exe" start= auto obj= "NT AUTHORITY\LocalService" password= " "
- 基础读写能力验证:编写的测试服务可正常写入并读取自身创建的文件,证明服务本身具备基础文件读写能力,测试服务核心代码如下:
int main(int argc, char* argv[]) { std::string path = config::get_executable_path(); cfg.log_path = path; events::start_log(cfg.log_path, "trace", true); // 服务程序通过SERVICE_TABLE_ENTRY结构声明可启动的服务,指定服务名和服务主函数指针 // 服务主函数调用StartServiceCtrlDispatcher连接服务控制管理器(SCM) SERVICE_TABLE_ENTRY service_table[] = { { (LPSTR)service_name, (LPSERVICE_MAIN_FUNCTION)service_main }, { NULL, NULL } }; if (StartServiceCtrlDispatcher(service_table)) { return 0; } else { return 1; } } ///////////////////////////////////////////////////////////////////////////////////////////////////// //service_main 服务主函数 ///////////////////////////////////////////////////////////////////////////////////////////////////// void WINAPI service_main(DWORD argc, LPTSTR* argv) { service_handle = RegisterServiceCtrlHandler(service_name, service_handler); if (service_handle == NULL) { return; } service_stop_event = CreateEvent(NULL, TRUE, FALSE, NULL); if (service_stop_event == NULL) { return; } report_status(SERVICE_START_PENDING); report_status(SERVICE_RUNNING); SPDLOG_INFO("service running..." + std::to_string(current_state)); HANDLE thread_service = 0; thread_service = CreateThread(NULL, 0, service_thread, NULL, 0, NULL); WaitForSingleObject(thread_service, INFINITE); ///////////////////////////////////////////////////////////////////////////////////////////////////// // 收到服务关闭请求 ///////////////////////////////////////////////////////////////////////////////////////////////////// CloseHandle(thread_service); report_status(SERVICE_STOP_PENDING); SPDLOG_INFO("service stop pending..." + std::to_string(current_state)); CloseHandle(service_stop_event); report_status(SERVICE_STOPPED); SPDLOG_INFO("service stopped..." + std::to_string(current_state)); } ///////////////////////////////////////////////////////////////////////////////////////////////////// //service_thread 服务工作线程 ///////////////////////////////////////////////////////////////////////////////////////////////////// DWORD WINAPI service_thread(LPVOID lpParam) { std::string path = cfg.log_path; SPDLOG_INFO("service started in..." + cfg.log_path); path += "\\test.txt"; size_t i = 0; while (WaitForSingleObject(service_stop_event, 0) != WAIT_OBJECT_0) { write_txt_file(path, "writing...#" + std::to_string(i)); i++; Sleep(10000); read_txt_file(path); } return ERROR_SUCCESS; } ///////////////////////////////////////////////////////////////////////////////////////////////////// //write_txt_file 文件写入函数 ///////////////////////////////////////////////////////////////////////////////////////////////////// void write_txt_file(const std::string& file_name, const std::string& input) { FILE* f = fopen(file_name.c_str(), "a+"); fprintf(f, "%s\n", input.c_str()); fclose(f); } ///////////////////////////////////////////////////////////////////////////////////////////////////// //read_txt_file 文件读取函数 ///////////////////////////////////////////////////////////////////////////////////////////////////// void read_txt_file(const std::string& file_name) { std::ifstream ifs; ifs.open(file_name); if (!ifs.is_open()) { SPDLOG_ERROR("Cannot open: " + file_name); return; } std::string line; while (std::getline(ifs, line)) { SPDLOG_INFO("Line: " + line); } ifs.close(); }
- 权限差异验证:在Windows资源管理器中查看测试服务写入的文件属性,可见文件所有者为
LOCAL_SERVICE;而待读取的目标配置文件所有者为Administrators组,因此判定文件所有权是导致读取失败的核心原因。 - 配置文件读取逻辑:使用
std::ifstream以默认只读模式打开文件,实现代码如下:
int config::config_t::read(const std::string& fname) { try { std::ifstream ifs(fname); ifs >> configuration_json; ifs.close(); from_json(configuration_json, *this); } catch (const std::exception& e) { SPDLOG_ERROR(e.what()); return -1; } return 0; }
待解决问题
- 如何通过PowerShell脚本修改目标配置文件的所有权/权限,使服务可正常读取该文件
- 是否可在创建服务时配置对应权限,使其能够读取目标文件
内容的提问来源于stack exchange,提问作者Pedro Vicente
相关产品推荐
相关产品推荐

