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 <errno.h>
20 #include <fcntl.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     if (ret == 0) {
243         ALOGV("setFileCon(%s, %s) => %d", path.c_str(), context.c_str(), ret);
244         return true;
245     }
246     ALOGE("setFileCon(%s, %s) => %d, err: %s", path.c_str(), context.c_str(), ret, strerror(errno));
247     return false;
248 }
249 
250 /*
251  * Function: getFileCon
252  * Purpose: retrieves the context associated with the given path in the file system
253  * Parameters:
254  *        path: given path in the file system
255  * Returns:
256  *        string representing the security context string of the file object
257  *        the string may be NULL if an error occured
258  * Exceptions: NullPointerException if the path object is null
259  */
getFileCon(JNIEnv * env,jobject,jstring pathStr)260 static jstring getFileCon(JNIEnv *env, jobject, jstring pathStr) {
261     if (isSELinuxDisabled) {
262         return NULL;
263     }
264 
265     ScopedUtfChars path(env, pathStr);
266     if (path.c_str() == NULL) {
267         return NULL;
268     }
269 
270     security_context_t tmp = NULL;
271     int ret = getfilecon(path.c_str(), &tmp);
272     Unique_SecurityContext context(tmp);
273 
274     ScopedLocalRef<jstring> securityString(env, NULL);
275     if (ret != -1) {
276         securityString.reset(env->NewStringUTF(context.get()));
277     }
278 
279     ALOGV("getFileCon(%s) => %s", path.c_str(), context.get());
280     return securityString.release();
281 }
282 
283 /*
284  * Function: getCon
285  * Purpose: Get the context of the current process.
286  * Parameters: none
287  * Returns: a jstring representing the security context of the process,
288  *          the jstring may be NULL if there was an error
289  * Exceptions: none
290  */
getCon(JNIEnv * env,jobject)291 static jstring getCon(JNIEnv *env, jobject) {
292     if (isSELinuxDisabled) {
293         return NULL;
294     }
295 
296     security_context_t tmp = NULL;
297     int ret = getcon(&tmp);
298     Unique_SecurityContext context(tmp);
299 
300     ScopedLocalRef<jstring> securityString(env, NULL);
301     if (ret != -1) {
302         securityString.reset(env->NewStringUTF(context.get()));
303     }
304 
305     ALOGV("getCon() => %s", context.get());
306     return securityString.release();
307 }
308 
309 /*
310  * Function: getPidCon
311  * Purpose: Get the context of a process identified by its pid
312  * Parameters:
313  *            pid: a jint representing the process
314  * Returns: a jstring representing the security context of the pid,
315  *          the jstring may be NULL if there was an error
316  * Exceptions: none
317  */
getPidCon(JNIEnv * env,jobject,jint pid)318 static jstring getPidCon(JNIEnv *env, jobject, jint pid) {
319     if (isSELinuxDisabled) {
320         return NULL;
321     }
322 
323     security_context_t tmp = NULL;
324     int ret = getpidcon(static_cast<pid_t>(pid), &tmp);
325     Unique_SecurityContext context(tmp);
326 
327     ScopedLocalRef<jstring> securityString(env, NULL);
328     if (ret != -1) {
329         securityString.reset(env->NewStringUTF(context.get()));
330     }
331 
332     ALOGV("getPidCon(%d) => %s", pid, context.get());
333     return securityString.release();
334 }
335 
336 /*
337  * Function: checkSELinuxAccess
338  * Purpose: Check permissions between two security contexts.
339  * Parameters: subjectContextStr: subject security context as a string
340  *             objectContextStr: object security context as a string
341  *             objectClassStr: object's security class name as a string
342  *             permissionStr: permission name as a string
343  * Returns: boolean: (true) if permission was granted, (false) otherwise
344  * Exceptions: None
345  */
checkSELinuxAccess(JNIEnv * env,jobject,jstring subjectContextStr,jstring objectContextStr,jstring objectClassStr,jstring permissionStr)346 static jboolean checkSELinuxAccess(JNIEnv *env, jobject, jstring subjectContextStr,
347         jstring objectContextStr, jstring objectClassStr, jstring permissionStr) {
348     if (isSELinuxDisabled) {
349         return true;
350     }
351 
352     ScopedUtfChars subjectContext(env, subjectContextStr);
353     if (subjectContext.c_str() == NULL) {
354         return false;
355     }
356 
357     ScopedUtfChars objectContext(env, objectContextStr);
358     if (objectContext.c_str() == NULL) {
359         return false;
360     }
361 
362     ScopedUtfChars objectClass(env, objectClassStr);
363     if (objectClass.c_str() == NULL) {
364         return false;
365     }
366 
367     ScopedUtfChars permission(env, permissionStr);
368     if (permission.c_str() == NULL) {
369         return false;
370     }
371 
372     char *tmp1 = const_cast<char *>(subjectContext.c_str());
373     char *tmp2 = const_cast<char *>(objectContext.c_str());
374     int accessGranted = selinux_check_access(tmp1, tmp2, objectClass.c_str(), permission.c_str(),
375             NULL);
376 
377     ALOGV("checkSELinuxAccess(%s, %s, %s, %s) => %d", subjectContext.c_str(), objectContext.c_str(),
378             objectClass.c_str(), permission.c_str(), accessGranted);
379 
380     return (accessGranted == 0) ? true : false;
381 }
382 
383 /*
384  * Function: native_restorecon
385  * Purpose: restore default SELinux security context
386  * Parameters: pathname: the pathname for the file to be relabeled
387  * Returns: boolean: (true) file label successfully restored, (false) otherwise
388  * Exceptions: none
389  */
native_restorecon(JNIEnv * env,jobject,jstring pathnameStr,jint flags)390 static jboolean native_restorecon(JNIEnv *env, jobject, jstring pathnameStr, jint flags) {
391     if (isSELinuxDisabled) {
392         return true;
393     }
394 
395     ScopedUtfChars pathname(env, pathnameStr);
396     if (pathname.c_str() == NULL) {
397         ALOGV("restorecon(%p) => threw exception", pathnameStr);
398         return false;
399     }
400 
401     int ret = selinux_android_restorecon(pathname.c_str(), flags);
402     ALOGV("restorecon(%s) => %d", pathname.c_str(), ret);
403     return (ret == 0);
404 }
405 
406 /*
407  * JNI registration.
408  */
409 static const JNINativeMethod method_table[] = {
410     /* name,                     signature,                    funcPtr */
411     { "checkSELinuxAccess"       , "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z" , (void*)checkSELinuxAccess },
412     { "getContext"               , "()Ljava/lang/String;"                         , (void*)getCon           },
413     { "getFileContext"           , "(Ljava/lang/String;)Ljava/lang/String;"       , (void*)getFileCon       },
414     { "getPeerContext"           , "(Ljava/io/FileDescriptor;)Ljava/lang/String;" , (void*)getPeerCon       },
415     { "getFileContext"           , "(Ljava/io/FileDescriptor;)Ljava/lang/String;" , (void*)getFdCon         },
416     { "getPidContext"            , "(I)Ljava/lang/String;"                        , (void*)getPidCon        },
417     { "isSELinuxEnforced"        , "()Z"                                          , (void*)isSELinuxEnforced},
418     { "isSELinuxEnabled"         , "()Z"                                          , (void*)isSELinuxEnabled },
419     { "native_restorecon"        , "(Ljava/lang/String;I)Z"                       , (void*)native_restorecon},
420     { "setFileContext"           , "(Ljava/lang/String;Ljava/lang/String;)Z"      , (void*)setFileCon       },
421     { "setFSCreateContext"       , "(Ljava/lang/String;)Z"                        , (void*)setFSCreateCon   },
422     { "fileSelabelLookup"        , "(Ljava/lang/String;)Ljava/lang/String;"       , (void*)fileSelabelLookup},
423 };
424 
log_callback(int type,const char * fmt,...)425 static int log_callback(int type, const char *fmt, ...) {
426     va_list ap;
427     int priority;
428 
429     switch (type) {
430     case SELINUX_WARNING:
431         priority = ANDROID_LOG_WARN;
432         break;
433     case SELINUX_INFO:
434         priority = ANDROID_LOG_INFO;
435         break;
436     default:
437         priority = ANDROID_LOG_ERROR;
438         break;
439     }
440     va_start(ap, fmt);
441     LOG_PRI_VA(priority, "SELinux", fmt, ap);
442     va_end(ap);
443     return 0;
444 }
445 
register_android_os_SELinux(JNIEnv * env)446 int register_android_os_SELinux(JNIEnv *env) {
447     union selinux_callback cb;
448     cb.func_log = log_callback;
449     selinux_set_callback(SELINUX_CB_LOG, cb);
450 
451     isSELinuxDisabled = (is_selinux_enabled() != 1) ? true : false;
452 
453     return RegisterMethodsOrDie(env, "android/os/SELinux", method_table, NELEM(method_table));
454 }
455 
456 }
457