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