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