1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #define LOG_TAG "SELinuxJNI"
18 #include <utils/Log.h>
19 
20 #include <nativehelper/JNIHelp.h>
21 #include "jni.h"
22 #include "core_jni_helpers.h"
23 #include "selinux/selinux.h"
24 #include "selinux/android.h"
25 #include <errno.h>
26 #include <memory>
27 #include <atomic>
28 #include <nativehelper/ScopedLocalRef.h>
29 #include <nativehelper/ScopedUtfChars.h>
30 
31 namespace android {
32 namespace {
33 std::atomic<selabel_handle*> sehandle{nullptr};
34 
GetSELabelHandle()35 selabel_handle* GetSELabelHandle() {
36     selabel_handle* h = sehandle.load();
37     if (h != nullptr) {
38         return h;
39     }
40 
41     h = selinux_android_file_context_handle();
42     selabel_handle* expected = nullptr;
43     if (!sehandle.compare_exchange_strong(expected, h)) {
44         selabel_close(h);
45         return sehandle.load();
46     }
47     return h;
48 }
49 
50 }
51 
52 struct SecurityContext_Delete {
operator ()android::SecurityContext_Delete53     void operator()(security_context_t p) const {
54         freecon(p);
55     }
56 };
57 typedef std::unique_ptr<char[], SecurityContext_Delete> Unique_SecurityContext;
58 
59 static jboolean isSELinuxDisabled = true;
60 
61 /*
62  * Function: isSELinuxEnabled
63  * Purpose:  checks whether SELinux is enabled/disbaled
64  * Parameters: none
65  * Return value : true (enabled) or false (disabled)
66  * Exceptions: none
67  */
isSELinuxEnabled(JNIEnv * env,jobject)68 static jboolean isSELinuxEnabled(JNIEnv *env, jobject) {
69     return !isSELinuxDisabled;
70 }
71 
72 /*
73  * Function: isSELinuxEnforced
74  * Purpose: return the current SELinux enforce mode
75  * Parameters: none
76  * Return value: true (enforcing) or false (permissive)
77  * Exceptions: none
78  */
isSELinuxEnforced(JNIEnv * env,jobject)79 static jboolean isSELinuxEnforced(JNIEnv *env, jobject) {
80     return (security_getenforce() == 1) ? true : false;
81 }
82 
fileSelabelLookup(JNIEnv * env,jobject,jstring pathStr)83 static jstring fileSelabelLookup(JNIEnv* env, jobject, jstring pathStr) {
84     if (isSELinuxDisabled) {
85         ALOGE("fileSelabelLookup => SELinux is disabled");
86         return NULL;
87     }
88 
89     if (pathStr == NULL) {
90       ALOGE("fileSelabelLookup => got null path.");
91       jniThrowNullPointerException(
92           env, "Trying to get security context of a null path.");
93       return NULL;
94     }
95 
96     ScopedUtfChars path(env, pathStr);
97     const char* path_c_str = path.c_str();
98     if (path_c_str == NULL) {
99         ALOGE("fileSelabelLookup => Got null path");
100         jniThrowNullPointerException(
101             env, "Trying to get security context of a null path.");
102         return NULL;
103     }
104 
105     auto* selabel_handle = GetSELabelHandle();
106     if (selabel_handle == NULL) {
107         ALOGE("fileSelabelLookup => Failed to get SEHandle");
108         return NULL;
109     }
110 
111     security_context_t tmp = NULL;
112     if (selabel_lookup(selabel_handle, &tmp, path_c_str, S_IFREG) != 0) {
113       ALOGE("fileSelabelLookup => selabel_lookup for %s failed: %d", path_c_str, errno);
114       return NULL;
115     }
116 
117     Unique_SecurityContext context(tmp);
118     return env->NewStringUTF(context.get());
119 }
120 
getFdConInner(JNIEnv * env,jobject fileDescriptor,bool isSocket)121 static jstring getFdConInner(JNIEnv *env, jobject fileDescriptor, bool isSocket) {
122     if (isSELinuxDisabled) {
123         return NULL;
124     }
125 
126     if (fileDescriptor == NULL) {
127         jniThrowNullPointerException(env,
128                 "Trying to check security context of a null FileDescriptor.");
129         return NULL;
130     }
131 
132     int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
133     if (env->ExceptionCheck()) {
134         ALOGE("getFdCon => getFD for %p failed", fileDescriptor);
135         return NULL;
136     }
137 
138     security_context_t tmp = NULL;
139     int ret;
140     if (isSocket) {
141         ret = getpeercon(fd, &tmp);
142     } else{
143         ret = fgetfilecon(fd, &tmp);
144     }
145     Unique_SecurityContext context(tmp);
146 
147     ScopedLocalRef<jstring> contextStr(env, NULL);
148     if (ret != -1) {
149         contextStr.reset(env->NewStringUTF(context.get()));
150     }
151 
152     ALOGV("getFdCon(%d) => %s", fd, context.get());
153     return contextStr.release();
154 }
155 
156 /*
157  * Function: getPeerCon
158  * Purpose: retrieves security context of peer socket
159  * Parameters:
160  *        fileDescriptor: peer socket file as a FileDescriptor object
161  * Returns: jstring representing the security_context of socket or NULL if error
162  * Exceptions: NullPointerException if fileDescriptor object is NULL
163  */
getPeerCon(JNIEnv * env,jobject,jobject fileDescriptor)164 static jstring getPeerCon(JNIEnv *env, jobject, jobject fileDescriptor) {
165     return getFdConInner(env, fileDescriptor, true);
166 }
167 
168 /*
169  * Function: getFdCon
170  * Purpose: retrieves security context of a file descriptor.
171  * Parameters:
172  *        fileDescriptor: a FileDescriptor object
173  * Returns: jstring representing the security_context of socket or NULL if error
174  * Exceptions: NullPointerException if fileDescriptor object is NULL
175  */
getFdCon(JNIEnv * env,jobject,jobject fileDescriptor)176 static jstring getFdCon(JNIEnv *env, jobject, jobject fileDescriptor) {
177     return getFdConInner(env, fileDescriptor, false);
178 }
179 
180 /*
181  * Function: setFSCreateCon
182  * Purpose: set security context used for creating a new file system object
183  * Parameters:
184  *       context: security_context_t representing the new context of a file system object,
185  *                set to NULL to return to the default policy behavior
186  * Returns: true on success, false on error
187  * Exception: none
188  */
setFSCreateCon(JNIEnv * env,jobject,jstring contextStr)189 static jboolean setFSCreateCon(JNIEnv *env, jobject, jstring contextStr) {
190     if (isSELinuxDisabled) {
191         return false;
192     }
193 
194     std::unique_ptr<ScopedUtfChars> context;
195     const char* context_c_str = NULL;
196     if (contextStr != NULL) {
197         context.reset(new ScopedUtfChars(env, contextStr));
198         context_c_str = context->c_str();
199         if (context_c_str == NULL) {
200             return false;
201         }
202     }
203 
204     int ret = setfscreatecon(const_cast<char *>(context_c_str));
205 
206     ALOGV("setFSCreateCon(%s) => %d", context_c_str, ret);
207 
208     return (ret == 0) ? true : false;
209 }
210 
211 /*
212  * Function: setFileCon
213  * Purpose:  set the security context of a file object
214  * Parameters:
215  *       path: the location of the file system object
216  *       context: the new security context of the file system object
217  * Returns: true on success, false on error
218  * Exception: NullPointerException is thrown if either path or context strign are NULL
219  */
setFileCon(JNIEnv * env,jobject,jstring pathStr,jstring contextStr)220 static jboolean setFileCon(JNIEnv *env, jobject, jstring pathStr, jstring contextStr) {
221     if (isSELinuxDisabled) {
222         return false;
223     }
224 
225     ScopedUtfChars path(env, pathStr);
226     if (path.c_str() == NULL) {
227         return false;
228     }
229 
230     ScopedUtfChars context(env, contextStr);
231     if (context.c_str() == NULL) {
232         return false;
233     }
234 
235     // GetStringUTFChars returns const char * yet setfilecon needs char *
236     char *tmp = const_cast<char *>(context.c_str());
237     int ret = setfilecon(path.c_str(), tmp);
238 
239     ALOGV("setFileCon(%s, %s) => %d", path.c_str(), context.c_str(), ret);
240     return (ret == 0) ? true : false;
241 }
242 
243 /*
244  * Function: getFileCon
245  * Purpose: retrieves the context associated with the given path in the file system
246  * Parameters:
247  *        path: given path in the file system
248  * Returns:
249  *        string representing the security context string of the file object
250  *        the string may be NULL if an error occured
251  * Exceptions: NullPointerException if the path object is null
252  */
getFileCon(JNIEnv * env,jobject,jstring pathStr)253 static jstring getFileCon(JNIEnv *env, jobject, jstring pathStr) {
254     if (isSELinuxDisabled) {
255         return NULL;
256     }
257 
258     ScopedUtfChars path(env, pathStr);
259     if (path.c_str() == NULL) {
260         return NULL;
261     }
262 
263     security_context_t tmp = NULL;
264     int ret = getfilecon(path.c_str(), &tmp);
265     Unique_SecurityContext context(tmp);
266 
267     ScopedLocalRef<jstring> securityString(env, NULL);
268     if (ret != -1) {
269         securityString.reset(env->NewStringUTF(context.get()));
270     }
271 
272     ALOGV("getFileCon(%s) => %s", path.c_str(), context.get());
273     return securityString.release();
274 }
275 
276 /*
277  * Function: getCon
278  * Purpose: Get the context of the current process.
279  * Parameters: none
280  * Returns: a jstring representing the security context of the process,
281  *          the jstring may be NULL if there was an error
282  * Exceptions: none
283  */
getCon(JNIEnv * env,jobject)284 static jstring getCon(JNIEnv *env, jobject) {
285     if (isSELinuxDisabled) {
286         return NULL;
287     }
288 
289     security_context_t tmp = NULL;
290     int ret = getcon(&tmp);
291     Unique_SecurityContext context(tmp);
292 
293     ScopedLocalRef<jstring> securityString(env, NULL);
294     if (ret != -1) {
295         securityString.reset(env->NewStringUTF(context.get()));
296     }
297 
298     ALOGV("getCon() => %s", context.get());
299     return securityString.release();
300 }
301 
302 /*
303  * Function: getPidCon
304  * Purpose: Get the context of a process identified by its pid
305  * Parameters:
306  *            pid: a jint representing the process
307  * Returns: a jstring representing the security context of the pid,
308  *          the jstring may be NULL if there was an error
309  * Exceptions: none
310  */
getPidCon(JNIEnv * env,jobject,jint pid)311 static jstring getPidCon(JNIEnv *env, jobject, jint pid) {
312     if (isSELinuxDisabled) {
313         return NULL;
314     }
315 
316     security_context_t tmp = NULL;
317     int ret = getpidcon(static_cast<pid_t>(pid), &tmp);
318     Unique_SecurityContext context(tmp);
319 
320     ScopedLocalRef<jstring> securityString(env, NULL);
321     if (ret != -1) {
322         securityString.reset(env->NewStringUTF(context.get()));
323     }
324 
325     ALOGV("getPidCon(%d) => %s", pid, context.get());
326     return securityString.release();
327 }
328 
329 /*
330  * Function: checkSELinuxAccess
331  * Purpose: Check permissions between two security contexts.
332  * Parameters: subjectContextStr: subject security context as a string
333  *             objectContextStr: object security context as a string
334  *             objectClassStr: object's security class name as a string
335  *             permissionStr: permission name as a string
336  * Returns: boolean: (true) if permission was granted, (false) otherwise
337  * Exceptions: None
338  */
checkSELinuxAccess(JNIEnv * env,jobject,jstring subjectContextStr,jstring objectContextStr,jstring objectClassStr,jstring permissionStr)339 static jboolean checkSELinuxAccess(JNIEnv *env, jobject, jstring subjectContextStr,
340         jstring objectContextStr, jstring objectClassStr, jstring permissionStr) {
341     if (isSELinuxDisabled) {
342         return true;
343     }
344 
345     ScopedUtfChars subjectContext(env, subjectContextStr);
346     if (subjectContext.c_str() == NULL) {
347         return false;
348     }
349 
350     ScopedUtfChars objectContext(env, objectContextStr);
351     if (objectContext.c_str() == NULL) {
352         return false;
353     }
354 
355     ScopedUtfChars objectClass(env, objectClassStr);
356     if (objectClass.c_str() == NULL) {
357         return false;
358     }
359 
360     ScopedUtfChars permission(env, permissionStr);
361     if (permission.c_str() == NULL) {
362         return false;
363     }
364 
365     char *tmp1 = const_cast<char *>(subjectContext.c_str());
366     char *tmp2 = const_cast<char *>(objectContext.c_str());
367     int accessGranted = selinux_check_access(tmp1, tmp2, objectClass.c_str(), permission.c_str(),
368             NULL);
369 
370     ALOGV("checkSELinuxAccess(%s, %s, %s, %s) => %d", subjectContext.c_str(), objectContext.c_str(),
371             objectClass.c_str(), permission.c_str(), accessGranted);
372 
373     return (accessGranted == 0) ? true : false;
374 }
375 
376 /*
377  * Function: native_restorecon
378  * Purpose: restore default SELinux security context
379  * Parameters: pathname: the pathname for the file to be relabeled
380  * Returns: boolean: (true) file label successfully restored, (false) otherwise
381  * Exceptions: none
382  */
native_restorecon(JNIEnv * env,jobject,jstring pathnameStr,jint flags)383 static jboolean native_restorecon(JNIEnv *env, jobject, jstring pathnameStr, jint flags) {
384     if (isSELinuxDisabled) {
385         return true;
386     }
387 
388     ScopedUtfChars pathname(env, pathnameStr);
389     if (pathname.c_str() == NULL) {
390         ALOGV("restorecon(%p) => threw exception", pathnameStr);
391         return false;
392     }
393 
394     int ret = selinux_android_restorecon(pathname.c_str(), flags);
395     ALOGV("restorecon(%s) => %d", pathname.c_str(), ret);
396     return (ret == 0);
397 }
398 
399 /*
400  * JNI registration.
401  */
402 static const JNINativeMethod method_table[] = {
403     /* name,                     signature,                    funcPtr */
404     { "checkSELinuxAccess"       , "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z" , (void*)checkSELinuxAccess },
405     { "getContext"               , "()Ljava/lang/String;"                         , (void*)getCon           },
406     { "getFileContext"           , "(Ljava/lang/String;)Ljava/lang/String;"       , (void*)getFileCon       },
407     { "getPeerContext"           , "(Ljava/io/FileDescriptor;)Ljava/lang/String;" , (void*)getPeerCon       },
408     { "getFileContext"           , "(Ljava/io/FileDescriptor;)Ljava/lang/String;" , (void*)getFdCon         },
409     { "getPidContext"            , "(I)Ljava/lang/String;"                        , (void*)getPidCon        },
410     { "isSELinuxEnforced"        , "()Z"                                          , (void*)isSELinuxEnforced},
411     { "isSELinuxEnabled"         , "()Z"                                          , (void*)isSELinuxEnabled },
412     { "native_restorecon"        , "(Ljava/lang/String;I)Z"                       , (void*)native_restorecon},
413     { "setFileContext"           , "(Ljava/lang/String;Ljava/lang/String;)Z"      , (void*)setFileCon       },
414     { "setFSCreateContext"       , "(Ljava/lang/String;)Z"                        , (void*)setFSCreateCon   },
415     { "fileSelabelLookup"        , "(Ljava/lang/String;)Ljava/lang/String;"       , (void*)fileSelabelLookup},
416 };
417 
log_callback(int type,const char * fmt,...)418 static int log_callback(int type, const char *fmt, ...) {
419     va_list ap;
420     int priority;
421 
422     switch (type) {
423     case SELINUX_WARNING:
424         priority = ANDROID_LOG_WARN;
425         break;
426     case SELINUX_INFO:
427         priority = ANDROID_LOG_INFO;
428         break;
429     default:
430         priority = ANDROID_LOG_ERROR;
431         break;
432     }
433     va_start(ap, fmt);
434     LOG_PRI_VA(priority, "SELinux", fmt, ap);
435     va_end(ap);
436     return 0;
437 }
438 
register_android_os_SELinux(JNIEnv * env)439 int register_android_os_SELinux(JNIEnv *env) {
440     union selinux_callback cb;
441     cb.func_log = log_callback;
442     selinux_set_callback(SELINUX_CB_LOG, cb);
443 
444     isSELinuxDisabled = (is_selinux_enabled() != 1) ? true : false;
445 
446     return RegisterMethodsOrDie(env, "android/os/SELinux", method_table, NELEM(method_table));
447 }
448 
449 }
450