1 /*
2  * Copyright (c) 2022 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "service_checker.h"
17 
18 #include <cctype>
19 #include <cerrno>
20 #include <cstdarg>
21 #include <cstddef>
22 #include <fstream>
23 #include <istream>
24 #include <mutex>
25 #include <sstream>
26 #include <streambuf>
27 #include <string>
28 #include <unordered_map>
29 #include <utility>
30 
31 #include <pthread.h>
32 #include "selinux/selinux.h"
33 
34 #include "src/callbacks.h"
35 #include "securec.h"
36 
37 #include "selinux_error.h"
38 #include "selinux_log.h"
39 
40 using namespace Selinux;
41 
42 namespace {
43 static const std::string OBJECT_PREFIX = "u:object_r:";
44 static const std::string DEFAULT_CONTEXT = "u:object_r:default_service:s0";
45 static const std::string DEFAULT_HDF_CONTEXT = "u:object_r:default_hdf_service:s0";
46 static const int CONTEXTS_LENGTH_MIN = 16; // sizeof("x u:object_r:x:s0")
47 static const int CONTEXTS_LENGTH_MAX = 1024;
48 static pthread_once_t g_fcOnce = PTHREAD_ONCE_INIT;
49 static std::unordered_map<std::string, struct ServiceInfo> g_serviceMap;
50 std::mutex g_selinuxLock;
51 std::mutex g_loadContextsLock;
52 static const std::vector<std::string> SERVICE_CONTEXTS_FILE = {
53     "/system/etc/selinux/targeted/contexts/service_contexts",
54     "/vendor/etc/selinux/targeted/contexts/service_contexts",
55 };
56 static const std::vector<std::string> HDF_SERVICE_CONTEXTS_FILE = {
57     "/system/etc/selinux/targeted/contexts/hdf_service_contexts",
58     "/vendor/etc/selinux/targeted/contexts/hdf_service_contexts",
59 };
60 } // namespace
61 
HdfListServiceCheck(const char * callingSid)62 extern "C" int HdfListServiceCheck(const char *callingSid)
63 {
64     if (callingSid == nullptr) {
65         return -SELINUX_PTR_NULL;
66     }
67     return ServiceChecker::GetInstance().ListServiceCheck(callingSid);
68 }
69 
HdfGetServiceCheck(const char * callingSid,const char * serviceName)70 extern "C" int HdfGetServiceCheck(const char *callingSid, const char *serviceName)
71 {
72     if (callingSid == nullptr || serviceName == nullptr) {
73         return -SELINUX_PTR_NULL;
74     }
75     return ServiceChecker::GetInstance().GetServiceCheck(callingSid, serviceName);
76 }
77 
HdfAddServiceCheck(const char * callingSid,const char * serviceName)78 extern "C" int HdfAddServiceCheck(const char *callingSid, const char *serviceName)
79 {
80     if (callingSid == nullptr || serviceName == nullptr) {
81         return -SELINUX_PTR_NULL;
82     }
83     return ServiceChecker::GetInstance().AddServiceCheck(callingSid, serviceName);
84 }
85 
86 struct AuditMsg {
87     const char *sid;
88     const char *name;
89 };
90 
SelinuxAuditCallback(void * data,security_class_t cls,char * buf,size_t len)91 static int SelinuxAuditCallback(void *data, security_class_t cls, char *buf, size_t len)
92 {
93     if (data == nullptr || buf == nullptr || len == 0) {
94         return -1;
95     }
96     auto *msg = reinterpret_cast<AuditMsg *>(data);
97     if (!msg->name) {
98         selinux_log(SELINUX_ERROR, "audit msg invalid argument\n");
99         return -1;
100     }
101     if (snprintf_s(buf, len, len - 1, "service=%s sid=%s", msg->name, msg->sid) <= 0) {
102         return -1;
103     }
104     return 0;
105 }
106 
SelinuxSetCallback()107 static void SelinuxSetCallback()
108 {
109     SetSelinuxHilogLevel(SELINUX_HILOG_ERROR);
110     union selinux_callback cb;
111     cb.func_log = SelinuxHilog;
112     selinux_set_callback(SELINUX_CB_LOG, cb);
113     cb.func_audit = SelinuxAuditCallback;
114     selinux_set_callback(SELINUX_CB_AUDIT, cb);
115 }
116 
CouldSkip(const std::string & line)117 static bool CouldSkip(const std::string &line)
118 {
119     if (line.size() < CONTEXTS_LENGTH_MIN || line.size() > CONTEXTS_LENGTH_MAX) {
120         return true;
121     }
122     int i = 0;
123     while (isspace(line[i])) {
124         i++;
125     }
126     if (line[i] == '#') {
127         return true;
128     }
129     if (line.find(OBJECT_PREFIX) == line.npos) {
130         return true;
131     }
132     return false;
133 }
134 
StartWith(const std::string & dst,const std::string & prefix)135 static bool StartWith(const std::string &dst, const std::string &prefix)
136 {
137     return dst.compare(0, prefix.size(), prefix) == 0;
138 }
139 
DecodeString(const std::string & line)140 static struct ServiceInfo DecodeString(const std::string &line)
141 {
142     std::stringstream input(line);
143     struct ServiceInfo contextBuff = {"", ""};
144     std::string name;
145     if (input >> name) {
146         contextBuff.serviceName = name;
147     }
148     std::string context;
149     if (input >> context) {
150         if (StartWith(context, OBJECT_PREFIX)) {
151             contextBuff.serviceContext = context;
152         }
153     }
154     return contextBuff;
155 }
156 
CheckServiceNameValid(const std::string & serviceName)157 static int CheckServiceNameValid(const std::string &serviceName)
158 {
159     if (serviceName.empty() || serviceName[0] == '.') {
160         return -SELINUX_ARG_INVALID;
161     }
162     return SELINUX_SUCC;
163 }
164 
ServiceContextsLoad(const std::vector<std::string> & fileName)165 static bool ServiceContextsLoad(const std::vector<std::string> &fileName)
166 {
167     // load service_contexts file
168     for (const auto &file : fileName) {
169         std::ifstream contextsFile(file);
170         if (!contextsFile) {
171             selinux_log(SELINUX_ERROR, "Load service_contexts fail, no such file: %s\n", file.c_str());
172             continue;
173         }
174         int lineNum = 0;
175         std::string line;
176         while (getline(contextsFile, line)) {
177             lineNum++;
178             if (CouldSkip(line)) {
179                 continue;
180             }
181             struct ServiceInfo tmpInfo = DecodeString(line);
182             if (!tmpInfo.serviceContext.empty() && !tmpInfo.serviceName.empty()) {
183                 g_serviceMap.emplace(tmpInfo.serviceName, tmpInfo);
184             } else {
185                 selinux_log(SELINUX_ERROR, "service_contexts read fail in line %d\n", lineNum);
186             }
187         }
188         selinux_log(SELINUX_INFO, "Load service_contexts success: %s\n", file.c_str());
189         contextsFile.close();
190     }
191     return !g_serviceMap.empty();
192 }
193 
ServiceChecker(bool isHdf)194 ServiceChecker::ServiceChecker(bool isHdf) : isHdf_(isHdf)
195 {
196     if (isHdf) {
197         serviceClass_ = "hdf_devmgr_class";
198     } else {
199         serviceClass_ = "samgr_class";
200     }
201     __selinux_once(g_fcOnce, SelinuxSetCallback);
202 }
203 
GetServiceContext(const std::string & serviceName,std::string & context)204 int ServiceChecker::GetServiceContext(const std::string &serviceName, std::string &context)
205 {
206     if (CheckServiceNameValid(serviceName) != 0) {
207         selinux_log(SELINUX_ERROR, "serviceName invalid!\n");
208         return -SELINUX_ARG_INVALID;
209     }
210     {
211         std::lock_guard<std::mutex> lock(g_loadContextsLock);
212         if (g_serviceMap.empty()) {
213             if (!ServiceContextsLoad(isHdf_ ? HDF_SERVICE_CONTEXTS_FILE : SERVICE_CONTEXTS_FILE)) {
214                 return -SELINUX_CONTEXTS_FILE_LOAD_ERROR;
215             }
216         }
217     }
218 
219     auto iter = g_serviceMap.find(serviceName);
220     if (iter != g_serviceMap.end()) {
221         context = iter->second.serviceContext;
222     } else {
223         context = isHdf_ ? DEFAULT_HDF_CONTEXT : DEFAULT_CONTEXT;
224     }
225 
226     return SELINUX_SUCC;
227 }
228 
GetThisContext(std::string & context)229 static int GetThisContext(std::string &context)
230 {
231     char *con = nullptr;
232     int rc = getcon(&con);
233     if (rc < 0) {
234         selinux_log(SELINUX_ERROR, "getcon failed!\n");
235         return -SELINUX_GET_CONTEXT_ERROR;
236     }
237     context = std::string(con);
238     freecon(con);
239     return SELINUX_SUCC;
240 }
241 
CheckPerm(const std::string & srcContext,const std::string & serviceName,std::string action)242 int ServiceChecker::CheckPerm(const std::string &srcContext, const std::string &serviceName, std::string action)
243 {
244     int ret = security_check_context(srcContext.c_str());
245     if (ret < 0) {
246         selinux_log(SELINUX_ERROR, "context: %s, %s\n", srcContext.c_str(), GetErrStr(SELINUX_CHECK_CONTEXT_ERROR));
247         return -SELINUX_CHECK_CONTEXT_ERROR;
248     }
249 
250     std::string destContext = "";
251     if (action == "list") {
252         ret = GetThisContext(destContext);
253     } else {
254         ret = GetServiceContext(serviceName, destContext);
255     }
256     if (ret != SELINUX_SUCC) {
257         return ret;
258     }
259     if (security_check_context(destContext.c_str()) < 0) {
260         selinux_log(SELINUX_ERROR, "context: %s, %s\n", destContext.c_str(), GetErrStr(SELINUX_CHECK_CONTEXT_ERROR));
261         return -SELINUX_CHECK_CONTEXT_ERROR;
262     }
263 
264     AuditMsg msg;
265     msg.name = serviceName.c_str();
266     msg.sid = srcContext.c_str();
267     {
268         std::lock_guard<std::mutex> lock(g_selinuxLock);
269         ret =  selinux_check_access(srcContext.c_str(), destContext.c_str(),
270             serviceClass_.c_str(), action.c_str(), &msg);
271     }
272     return ret == 0 ? SELINUX_SUCC : -SELINUX_PERMISSION_DENY;
273 }
274 
ListServiceCheck(const std::string & callingSid)275 int ServiceChecker::ListServiceCheck(const std::string &callingSid)
276 {
277     return CheckPerm(callingSid, serviceClass_, "list");
278 }
279 
GetServiceCheck(const std::string & callingSid,const std::string & serviceName)280 int ServiceChecker::GetServiceCheck(const std::string &callingSid, const std::string &serviceName)
281 {
282     return CheckPerm(callingSid, serviceName, "get");
283 }
284 
GetRemoteServiceCheck(const std::string & callingSid,const std::string & remoteServiceName)285 int ServiceChecker::GetRemoteServiceCheck(const std::string &callingSid, const std::string &remoteServiceName)
286 {
287     if (isHdf_) {
288         selinux_log(SELINUX_ERROR, "hdf service has no permission to get remote!\n");
289         return -SELINUX_PERMISSION_DENY;
290     }
291     return CheckPerm(callingSid, remoteServiceName, "get_remote");
292 }
293 
AddServiceCheck(const std::string & callingSid,const std::string & serviceName)294 int ServiceChecker::AddServiceCheck(const std::string &callingSid, const std::string &serviceName)
295 {
296     return CheckPerm(callingSid, serviceName, "add");
297 }
298 
GetInstance()299 ServiceChecker& ServiceChecker::GetInstance()
300 {
301     static ServiceChecker instance(true);
302     return instance;
303 }
304