Android.mk0100644 0000000 0000000 00000006046 12763246076 011541 0ustar000000000 0000000 LOCAL_PATH:= $(call my-dir) common_SRC_FILES := \ src/booleans.c \ src/canonicalize_context.c \ src/disable.c \ src/enabled.c \ src/fgetfilecon.c \ src/fsetfilecon.c \ src/getenforce.c \ src/getfilecon.c \ src/getpeercon.c \ src/lgetfilecon.c \ src/load_policy.c \ src/lsetfilecon.c \ src/policyvers.c \ src/procattr.c \ src/setenforce.c \ src/setfilecon.c \ src/context.c \ src/mapping.c \ src/stringrep.c \ src/compute_create.c \ src/compute_av.c \ src/avc.c \ src/avc_internal.c \ src/avc_sidtab.c \ src/get_initial_context.c \ src/checkAccess.c \ src/sestatus.c \ src/deny_unknown.c common_HOST_FILES := \ src/callbacks.c \ src/check_context.c \ src/freecon.c \ src/init.c \ src/label.c \ src/label_file.c \ src/label_android_property.c \ src/label_support.c include $(CLEAR_VARS) LOCAL_SRC_FILES := $(common_SRC_FILES) $(common_HOST_FILES) src/android.c LOCAL_MODULE:= libselinux LOCAL_MODULE_TAGS := eng LOCAL_STATIC_LIBRARIES := libcrypto_static LOCAL_C_INCLUDES := $(LOCAL_PATH)/include LOCAL_WHOLE_STATIC_LIBRARIES := libpcre libpackagelistparser # 1003 corresponds to auditd, from system/core/logd/event.logtags LOCAL_CFLAGS := -DAUDITD_LOG_TAG=1003 # mapping.c has redundant check of array p_in->perms. LOCAL_CLANG_CFLAGS += -Wno-pointer-bool-conversion LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include include $(BUILD_STATIC_LIBRARY) include $(CLEAR_VARS) LOCAL_CFLAGS := -DHOST ifeq ($(HOST_OS),darwin) LOCAL_CFLAGS += -DDARWIN endif LOCAL_SRC_FILES := $(common_HOST_FILES) LOCAL_MODULE:= libselinux LOCAL_MODULE_TAGS := eng LOCAL_WHOLE_STATIC_LIBRARIES := libpcre LOCAL_C_INCLUDES := $(LOCAL_PATH)/include LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include include $(BUILD_HOST_STATIC_LIBRARY) include $(CLEAR_VARS) LOCAL_SRC_FILES := $(common_SRC_FILES) $(common_HOST_FILES) src/android.c LOCAL_MODULE:= libselinux LOCAL_MODULE_TAGS := eng LOCAL_C_INCLUDES := $(LOCAL_PATH)/include LOCAL_SHARED_LIBRARIES := libcrypto liblog libpcre libpackagelistparser # 1003 corresponds to auditd, from system/core/logd/event.logtags LOCAL_CFLAGS := -DAUDITD_LOG_TAG=1003 # mapping.c has redundant check of array p_in->perms. LOCAL_CLANG_CFLAGS += -Wno-pointer-bool-conversion LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include include $(BUILD_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_CFLAGS := -DHOST ifeq ($(HOST_OS),darwin) LOCAL_CFLAGS += -DDARWIN endif LOCAL_SRC_FILES := $(common_HOST_FILES) LOCAL_MODULE:= libselinux LOCAL_MODULE_TAGS := eng LOCAL_WHOLE_STATIC_LIBRARIES := libpcre LOCAL_C_INCLUDES := $(LOCAL_PATH)/include LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include include $(BUILD_HOST_SHARED_LIBRARY) ################################# include $(CLEAR_VARS) LOCAL_CFLAGS := -DHOST ifeq ($(HOST_OS),darwin) LOCAL_CFLAGS += -DDARWIN endif LOCAL_MODULE := sefcontext_compile LOCAL_MODULE_TAGS := eng LOCAL_C_INCLUDES := ../src/label_file.h LOCAL_SRC_FILES := utils/sefcontext_compile.c LOCAL_STATIC_LIBRARIES := libselinux LOCAL_WHOLE_STATIC_LIBRARIES := libpcre LOCAL_C_INCLUDES := external/pcre include $(BUILD_HOST_EXECUTABLE) MODULE_LICENSE_PUBLIC_DOMAIN0100644 0000000 0000000 00000000000 12763246076 013747 0ustar000000000 0000000 NOTICE0100644 0000000 0000000 00000002012 12763246076 010521 0ustar000000000 0000000 This library (libselinux) is public domain software, i.e. not copyrighted. Warranty Exclusion ------------------ You agree that this software is a non-commercially developed program that may contain "bugs" (as that term is used in the industry) and that it may not function as intended. The software is licensed "as is". NSA makes no, and hereby expressly disclaims all, warranties, express, implied, statutory, or otherwise with respect to the software, including noninfringement and the implied warranties of merchantability and fitness for a particular purpose. Limitation of Liability ----------------------- In no event will NSA be liable for any damages, including loss of data, lost profits, cost of cover, or other special, incidental, consequential, direct or indirect damages arising from the software or the use thereof, however caused and on any theory of liability. This limitation will apply even if NSA has been advised of the possibility of such damage. You acknowledge that this is a reasonable allocation of risk. README.android0100644 0000000 0000000 00000005407 12763246076 012127 0ustar000000000 0000000 This directory contains a small port of libselinux for Android. It was originally forked in mid-2011, circa libselinux 2.1.0. Some changes have been cherry-picked from the upstream libselinux. Upstream git repository is https://github.com/SELinuxProject/selinux (libselinux subdirectory) and official releases are available from https://github.com/SELinuxProject/selinux/wiki/Releases. This fork differs from upstream libselinux in at least the following ways: * Dependencies on glibc-specific features have been removed/replaced in order to work with bionic, * Legacy code and compatibility interfaces have been removed, * Many interfaces, functions, and files are omitted since they are unused in Android, * The python bindings are omitted since they are unused in Android, * The setrans (context translation) support has been removed since there is no need for MLS label translation in Android and the support imposes extra overhead on calls passing security contexts, * The SELinux policy files are all located in / rather than under /etc/selinux since /etc is not available in Android until /system is mounted and use fixed paths, not dependent on /etc/selinux/config, * The kernel policy file (sepolicy in Android, policy.N in Linux) does not include a version suffix since Android does not need to support booting multiple kernels, * The policy loading logic does not support automatic downgrading of the kernel policy file to a version known to the kernel, since this requires libsepol on the device and is only needed to support mixing and matching kernels and userspace easily, * The selabel interface and label_file backend have been extended to support label-by-symlink and partial matching support for use by ueventd in labeling device nodes based on stable symlink names and by init for optimizing its restorecon_recursive of /sys, * Since the fork, upstream libselinux has switched the label_file backend to use a binary version of the file_contexts file (file_contexts.bin) that contains precompiled versions of the pcre regexes. This reduces the time to load the file_contexts configuration, which in Linux can be significant due to the large number of entries (> 5000). As Android has far fewer entries (~400), this has not yet seemed necessary. * restorecon functionality, including recursive restorecon, has been fully implemented within new libselinux functions, along with optimizations to prune the tree walk if no change has occurred in file_contexts since the last restorecon, * Support for new Android-specific SELinux configuration files, such as seapp_contexts, property_contexts, and service_contexts, has been added. New files added for Android: * libselinux/include/selinux/android.h * libselinux/src/android.c * libselinux/src/label_android_property.c (later added upstream) include/0040755 0000000 0000000 00000000000 12763246076 011250 5ustar000000000 0000000 include/selinux/0040755 0000000 0000000 00000000000 12763246076 012737 5ustar000000000 0000000 include/selinux/android.h0100644 0000000 0000000 00000003231 12763246076 014524 0ustar000000000 0000000 #ifndef _SELINUX_ANDROID_H_ #define _SELINUX_ANDROID_H_ #include #include #include #include #ifdef __cplusplus extern "C" { #endif extern struct selabel_handle* selinux_android_file_context_handle(void); extern struct selabel_handle* selinux_android_prop_context_handle(void); extern struct selabel_handle* selinux_android_service_context_handle(void); extern void selinux_android_set_sehandle(const struct selabel_handle *hndl); extern int selinux_android_load_policy(void); extern int selinux_android_reload_policy(void); extern int selinux_android_setcon(const char *con); extern int selinux_android_setcontext(uid_t uid, bool isSystemServer, const char *seinfo, const char *name); extern int selinux_android_setfilecon(const char *pkgdir, const char *pkgname, const char *seinfo, uid_t uid); extern int selinux_log_callback(int type, const char *fmt, ...) __attribute__ ((format(printf, 2, 3))); #define SELINUX_ANDROID_RESTORECON_NOCHANGE 1 #define SELINUX_ANDROID_RESTORECON_VERBOSE 2 #define SELINUX_ANDROID_RESTORECON_RECURSE 4 #define SELINUX_ANDROID_RESTORECON_FORCE 8 #define SELINUX_ANDROID_RESTORECON_DATADATA 16 extern int selinux_android_restorecon(const char *file, unsigned int flags); extern int selinux_android_restorecon_pkgdir(const char *pkgdir, const char *seinfo, uid_t uid, unsigned int flags); extern int selinux_android_seapp_context_reload(void); #ifdef __cplusplus } #endif #endif include/selinux/avc.h0100644 0000000 0000000 00000037777 12763246076 013703 0ustar000000000 0000000 /* * Access vector cache interface for object managers. * * Author : Eamon Walsh */ #ifndef _SELINUX_AVC_H_ #define _SELINUX_AVC_H_ #include #include #include #include #ifdef __cplusplus extern "C" { #endif /* * SID format and operations */ struct security_id { char * ctx; unsigned int refcnt; }; typedef struct security_id *security_id_t; #define SECSID_WILD (security_id_t)NULL /* unspecified SID */ /** * avc_sid_to_context - get copy of context corresponding to SID. * @sid: input SID * @ctx: pointer to context reference * * Return a copy of the security context corresponding to the input * @sid in the memory referenced by @ctx. The caller is expected to * free the context with freecon(). Return %0 on success, -%1 on * failure, with @errno set to %ENOMEM if insufficient memory was * available to make the copy, or %EINVAL if the input SID is invalid. */ int avc_sid_to_context(security_id_t sid, char ** ctx); int avc_sid_to_context_raw(security_id_t sid, char ** ctx); /** * avc_context_to_sid - get SID for context. * @ctx: input security context * @sid: pointer to SID reference * * Look up security context @ctx in SID table, making * a new entry if @ctx is not found. Increment the * reference counter for the SID. Store a pointer * to the SID structure into the memory referenced by @sid, * returning %0 on success or -%1 on error with @errno set. */ int avc_context_to_sid(const char * ctx, security_id_t * sid); int avc_context_to_sid_raw(const char * ctx, security_id_t * sid); /** * sidget - increment SID reference counter. * @sid: SID reference * * Increment the reference counter for @sid, indicating that * @sid is in use by an (additional) object. Return the * new reference count, or zero if @sid is invalid (has zero * reference count). Note that avc_context_to_sid() also * increments reference counts. */ int sidget(security_id_t sid); /** * sidput - decrement SID reference counter. * @sid: SID reference * * Decrement the reference counter for @sid, indicating that * a reference to @sid is no longer in use. Return the * new reference count. When the reference count reaches * zero, the SID is invalid, and avc_context_to_sid() must * be called to obtain a new SID for the security context. */ int sidput(security_id_t sid); /** * avc_get_initial_sid - get SID for an initial kernel security identifier * @name: input name of initial kernel security identifier * @sid: pointer to a SID reference * * Get the context for an initial kernel security identifier specified by * @name using security_get_initial_context() and then call * avc_context_to_sid() to get the corresponding SID. */ int avc_get_initial_sid(const char *name, security_id_t * sid); /* * AVC entry */ struct avc_entry; struct avc_entry_ref { struct avc_entry *ae; }; /** * avc_entry_ref_init - initialize an AVC entry reference. * @aeref: pointer to avc entry reference structure * * Use this macro to initialize an avc entry reference structure * before first use. These structures are passed to avc_has_perm(), * which stores cache entry references in them. They can increase * performance on repeated queries. */ #define avc_entry_ref_init(aeref) ((aeref)->ae = NULL) /* * User-provided callbacks for memory, auditing, and locking */ /* These structures are passed by reference to avc_init(). Passing * a NULL reference will cause the AVC to use a default. The default * memory callbacks are malloc() and free(). The default logging method * is to print on stderr. If no thread callbacks are passed, a separate * listening thread won't be started for kernel policy change messages. * If no locking callbacks are passed, no locking will take place. */ struct avc_memory_callback { /* malloc() equivalent. */ void *(*func_malloc) (size_t size); /* free() equivalent. */ void (*func_free) (void *ptr); /* Note that these functions should set errno on failure. If not, some avc routines may return -1 without errno set. */ }; struct avc_log_callback { /* log the printf-style format and arguments. */ void (*func_log) (const char *fmt, ...); /* store a string representation of auditdata (corresponding to the given security class) into msgbuf. */ void (*func_audit) (void *auditdata, security_class_t cls, char *msgbuf, size_t msgbufsize); }; struct avc_thread_callback { /* create and start a thread, returning an opaque pointer to it; the thread should run the given function. */ void *(*func_create_thread) (void (*run) (void)); /* cancel a given thread and free its resources. */ void (*func_stop_thread) (void *thread); }; struct avc_lock_callback { /* create a lock and return an opaque pointer to it. */ void *(*func_alloc_lock) (void); /* obtain a given lock, blocking if necessary. */ void (*func_get_lock) (void *lock); /* release a given lock. */ void (*func_release_lock) (void *lock); /* destroy a given lock (free memory, etc.) */ void (*func_free_lock) (void *lock); }; /* * Available options */ /* no-op option, useful for unused slots in an array of options */ #define AVC_OPT_UNUSED 0 /* override kernel enforcing mode (boolean value) */ #define AVC_OPT_SETENFORCE 1 /* * AVC operations */ /** * avc_init - Initialize the AVC. * @msgprefix: prefix for log messages * @mem_callbacks: user-supplied memory callbacks * @log_callbacks: user-supplied logging callbacks * @thread_callbacks: user-supplied threading callbacks * @lock_callbacks: user-supplied locking callbacks * * Initialize the access vector cache. Return %0 on * success or -%1 with @errno set on failure. * If @msgprefix is NULL, use "uavc". If any callback * structure references are NULL, use default methods * for those callbacks (see the definition of the callback * structures above). */ int avc_init(const char *msgprefix, const struct avc_memory_callback *mem_callbacks, const struct avc_log_callback *log_callbacks, const struct avc_thread_callback *thread_callbacks, const struct avc_lock_callback *lock_callbacks); /** * avc_open - Initialize the AVC. * @opts: array of selabel_opt structures specifying AVC options or NULL. * @nopts: number of elements in opts array or zero for no options. * * This function is identical to avc_init(), except the message prefix * is set to "avc" and any callbacks desired should be specified via * selinux_set_callback(). Available options are listed above. */ int avc_open(struct selinux_opt *opts, unsigned nopts); /** * avc_cleanup - Remove unused SIDs and AVC entries. * * Search the SID table for SID structures with zero * reference counts, and remove them along with all * AVC entries that reference them. This can be used * to return memory to the system. */ void avc_cleanup(void); /** * avc_reset - Flush the cache and reset statistics. * * Remove all entries from the cache and reset all access * statistics (as returned by avc_cache_stats()) to zero. * The SID mapping is not affected. Return %0 on success, * -%1 with @errno set on error. */ int avc_reset(void); /** * avc_destroy - Free all AVC structures. * * Destroy all AVC structures and free all allocated * memory. User-supplied locking, memory, and audit * callbacks will be retained, but security-event * callbacks will not. All SID's will be invalidated. * User must call avc_init() if further use of AVC is desired. */ void avc_destroy(void); /** * avc_has_perm_noaudit - Check permissions but perform no auditing. * @ssid: source security identifier * @tsid: target security identifier * @tclass: target security class * @requested: requested permissions, interpreted based on @tclass * @aeref: AVC entry reference * @avd: access vector decisions * * Check the AVC to determine whether the @requested permissions are granted * for the SID pair (@ssid, @tsid), interpreting the permissions * based on @tclass, and call the security server on a cache miss to obtain * a new decision and add it to the cache. Update @aeref to refer to an AVC * entry with the resulting decisions, and return a copy of the decisions * in @avd. Return %0 if all @requested permissions are granted, -%1 with * @errno set to %EACCES if any permissions are denied, or to another value * upon other errors. This function is typically called by avc_has_perm(), * but may also be called directly to separate permission checking from * auditing, e.g. in cases where a lock must be held for the check but * should be released for the auditing. */ int avc_has_perm_noaudit(security_id_t ssid, security_id_t tsid, security_class_t tclass, access_vector_t requested, struct avc_entry_ref *aeref, struct av_decision *avd); /** * avc_has_perm - Check permissions and perform any appropriate auditing. * @ssid: source security identifier * @tsid: target security identifier * @tclass: target security class * @requested: requested permissions, interpreted based on @tclass * @aeref: AVC entry reference * @auditdata: auxiliary audit data * * Check the AVC to determine whether the @requested permissions are granted * for the SID pair (@ssid, @tsid), interpreting the permissions * based on @tclass, and call the security server on a cache miss to obtain * a new decision and add it to the cache. Update @aeref to refer to an AVC * entry with the resulting decisions. Audit the granting or denial of * permissions in accordance with the policy. Return %0 if all @requested * permissions are granted, -%1 with @errno set to %EACCES if any permissions * are denied or to another value upon other errors. */ int avc_has_perm(security_id_t ssid, security_id_t tsid, security_class_t tclass, access_vector_t requested, struct avc_entry_ref *aeref, void *auditdata); /** * avc_audit - Audit the granting or denial of permissions. * @ssid: source security identifier * @tsid: target security identifier * @tclass: target security class * @requested: requested permissions * @avd: access vector decisions * @result: result from avc_has_perm_noaudit * @auditdata: auxiliary audit data * * Audit the granting or denial of permissions in accordance * with the policy. This function is typically called by * avc_has_perm() after a permission check, but can also be * called directly by callers who use avc_has_perm_noaudit() * in order to separate the permission check from the auditing. * For example, this separation is useful when the permission check must * be performed under a lock, to allow the lock to be released * before calling the auditing code. */ void avc_audit(security_id_t ssid, security_id_t tsid, security_class_t tclass, access_vector_t requested, struct av_decision *avd, int result, void *auditdata); /** * avc_compute_create - Compute SID for labeling a new object. * @ssid: source security identifier * @tsid: target security identifier * @tclass: target security class * @newsid: pointer to SID reference * * Call the security server to obtain a context for labeling a * new object. Look up the context in the SID table, making * a new entry if not found. Increment the reference counter * for the SID. Store a pointer to the SID structure into the * memory referenced by @newsid, returning %0 on success or -%1 on * error with @errno set. */ int avc_compute_create(security_id_t ssid, security_id_t tsid, security_class_t tclass, security_id_t * newsid); /** * avc_compute_member - Compute SID for polyinstantation. * @ssid: source security identifier * @tsid: target security identifier * @tclass: target security class * @newsid: pointer to SID reference * * Call the security server to obtain a context for labeling an * object instance. Look up the context in the SID table, making * a new entry if not found. Increment the reference counter * for the SID. Store a pointer to the SID structure into the * memory referenced by @newsid, returning %0 on success or -%1 on * error with @errno set. */ int avc_compute_member(security_id_t ssid, security_id_t tsid, security_class_t tclass, security_id_t * newsid); /* * security event callback facility */ /* security events */ #define AVC_CALLBACK_GRANT 1 #define AVC_CALLBACK_TRY_REVOKE 2 #define AVC_CALLBACK_REVOKE 4 #define AVC_CALLBACK_RESET 8 #define AVC_CALLBACK_AUDITALLOW_ENABLE 16 #define AVC_CALLBACK_AUDITALLOW_DISABLE 32 #define AVC_CALLBACK_AUDITDENY_ENABLE 64 #define AVC_CALLBACK_AUDITDENY_DISABLE 128 /** * avc_add_callback - Register a callback for security events. * @callback: callback function * @events: bitwise OR of desired security events * @ssid: source security identifier or %SECSID_WILD * @tsid: target security identifier or %SECSID_WILD * @tclass: target security class * @perms: permissions * * Register a callback function for events in the set @events * related to the SID pair (@ssid, @tsid) and * and the permissions @perms, interpreting * @perms based on @tclass. Returns %0 on success or * -%1 if insufficient memory exists to add the callback. */ int avc_add_callback(int (*callback) (uint32_t event, security_id_t ssid, security_id_t tsid, security_class_t tclass, access_vector_t perms, access_vector_t * out_retained), uint32_t events, security_id_t ssid, security_id_t tsid, security_class_t tclass, access_vector_t perms); /* * AVC statistics */ /* If set, cache statistics are tracked. This may * become a compile-time option in the future. */ #define AVC_CACHE_STATS 1 struct avc_cache_stats { unsigned entry_lookups; unsigned entry_hits; unsigned entry_misses; unsigned entry_discards; unsigned cav_lookups; unsigned cav_hits; unsigned cav_probes; unsigned cav_misses; }; /** * avc_cache_stats - get cache access statistics. * @stats: reference to statistics structure * * Fill the supplied structure with information about AVC * activity since the last call to avc_init() or * avc_reset(). See the structure definition for * details. */ void avc_cache_stats(struct avc_cache_stats *stats); /** * avc_av_stats - log av table statistics. * * Log a message with information about the size and * distribution of the access vector table. The audit * callback is used to print the message. */ void avc_av_stats(void); /** * avc_sid_stats - log SID table statistics. * * Log a message with information about the size and * distribution of the SID table. The audit callback * is used to print the message. */ void avc_sid_stats(void); /** * avc_netlink_open - Create a netlink socket and connect to the kernel. */ int avc_netlink_open(int blocking); /** * avc_netlink_loop - Wait for netlink messages from the kernel */ void avc_netlink_loop(void); /** * avc_netlink_close - Close the netlink socket */ void avc_netlink_close(void); /** * avc_netlink_acquire_fd - Acquire netlink socket fd. * * Allows the application to manage messages from the netlink socket in * its own main loop. */ int avc_netlink_acquire_fd(void); /** * avc_netlink_release_fd - Release netlink socket fd. * * Returns ownership of the netlink socket to the library. */ void avc_netlink_release_fd(void); /** * avc_netlink_check_nb - Check netlink socket for new messages. * * Called by the application when using avc_netlink_acquire_fd() to * process kernel netlink events. */ int avc_netlink_check_nb(void); /** * selinux_status_open - Open and map SELinux kernel status page * */ int selinux_status_open(int fallback); /** * selinux_status_close - Unmap and close SELinux kernel status page * */ void selinux_status_close(void); /** * selinux_status_updated - Inform us whether the kernel status has been updated * */ int selinux_status_updated(void); /** * selinux_status_getenforce - Get the enforce flag value * */ int selinux_status_getenforce(void); /** * selinux_status_policyload - Get the number of policy reloaded * */ int selinux_status_policyload(void); /** * selinux_status_deny_unknown - Get the behavior for undefined classes/permissions * */ int selinux_status_deny_unknown(void); #ifdef __cplusplus } #endif #endif /* _SELINUX_AVC_H_ */ include/selinux/context.h0100644 0000000 0000000 00000002271 12763246076 014573 0ustar000000000 0000000 #ifndef _SELINUX_CONTEXT_H_ #define _SELINUX_CONTEXT_H_ #ifdef __cplusplus extern "C" { #endif /* * Functions to deal with security contexts in user space. */ typedef struct { void *ptr; } context_s_t; typedef context_s_t *context_t; /* Return a new context initialized to a context string */ extern context_t context_new(const char *); /* * Return a pointer to the string value of the context_t * Valid until the next call to context_str or context_free * for the same context_t* */ extern char *context_str(context_t); /* Free the storage used by a context */ extern void context_free(context_t); /* Get a pointer to the string value of a context component */ extern const char *context_type_get(context_t); extern const char *context_range_get(context_t); extern const char *context_role_get(context_t); extern const char *context_user_get(context_t); /* Set a context component. Returns nonzero if unsuccessful */ extern int context_type_set(context_t, const char *); extern int context_range_set(context_t, const char *); extern int context_role_set(context_t, const char *); extern int context_user_set(context_t, const char *); #ifdef __cplusplus } #endif #endif include/selinux/label.h0100644 0000000 0000000 00000011375 12763246076 014173 0ustar000000000 0000000 /* * Labeling interface for userspace object managers and others. * * Author : Eamon Walsh */ #ifndef _SELABEL_H_ #define _SELABEL_H_ #include #include #include #ifdef __cplusplus extern "C" { #endif /* * Opaque type used for all label handles. */ struct selabel_handle; /* * Available backends. */ /* file contexts */ #define SELABEL_CTX_FILE 0 /* media contexts */ #define SELABEL_CTX_MEDIA 1 /* x contexts */ #define SELABEL_CTX_X 2 /* db objects */ #define SELABEL_CTX_DB 3 /* Android property service contexts */ #define SELABEL_CTX_ANDROID_PROP 4 /* * Available options */ /* no-op option, useful for unused slots in an array of options */ #define SELABEL_OPT_UNUSED 0 /* validate contexts before returning them (boolean value) */ #define SELABEL_OPT_VALIDATE 1 /* don't use local customizations to backend data (boolean value) */ #define SELABEL_OPT_BASEONLY 2 /* specify an alternate path to use when loading backend data */ #define SELABEL_OPT_PATH 3 /* select a subset of the search space as an optimization (file backend) */ #define SELABEL_OPT_SUBSET 4 /* total number of options */ #define SELABEL_NOPT 5 /* * Label operations */ /** * selabel_open - Create a labeling handle. * @backend: one of the constants specifying a supported labeling backend. * @opts: array of selabel_opt structures specifying label options or NULL. * @nopts: number of elements in opts array or zero for no options. * * Open a labeling backend for use. The available backend identifiers are * listed above. Options may be provided via the opts parameter; available * options are listed above. Not all options may be supported by every * backend. Return value is the created handle on success or NULL with * @errno set on failure. */ struct selabel_handle *selabel_open(unsigned int backend, const struct selinux_opt *opts, unsigned nopts); /** * selabel_close - Close a labeling handle. * @handle: specifies handle to close * * Destroy the specified handle, closing files, freeing allocated memory, * etc. The handle may not be further used after it has been closed. */ void selabel_close(struct selabel_handle *handle); /** * selabel_lookup - Perform labeling lookup operation. * @handle: specifies backend instance to query * @con: returns the appropriate context with which to label the object * @key: string input to lookup operation * @type: numeric input to the lookup operation * * Perform a labeling lookup operation. Return %0 on success, -%1 with * @errno set on failure. The key and type arguments are the inputs to the * lookup operation; appropriate values are dictated by the backend in use. * The result is returned in the memory pointed to by @con and must be freed * by the user with freecon(). */ int selabel_lookup(struct selabel_handle *handle, char **con, const char *key, int type); int selabel_lookup_raw(struct selabel_handle *handle, char **con, const char *key, int type); bool selabel_partial_match(struct selabel_handle *handle, const char *key); int selabel_lookup_best_match(struct selabel_handle *rec, char **con, const char *key, const char **aliases, int type); enum selabel_cmp_result { SELABEL_SUBSET, SELABEL_EQUAL, SELABEL_SUPERSET, SELABEL_INCOMPARABLE }; /** * selabel_cmp - Compare two label configurations. * @h1: handle for the first label configuration * @h2: handle for the first label configuration * * Compare two label configurations. * Return %SELABEL_SUBSET if @h1 is a subset of @h2, %SELABEL_EQUAL * if @h1 is identical to @h2, %SELABEL_SUPERSET if @h1 is a superset * of @h2, and %SELABEL_INCOMPARABLE if @h1 and @h2 are incomparable. */ enum selabel_cmp_result selabel_cmp(struct selabel_handle *h1, struct selabel_handle *h2); /** * selabel_stats - log labeling operation statistics. * @handle: specifies backend instance to query * * Log a message with information about the number of queries performed, * number of unused matching entries, or other operational statistics. * Message is backend-specific, some backends may not output a message. */ void selabel_stats(struct selabel_handle *handle); /* * Type codes used by specific backends */ /* X backend */ #define SELABEL_X_PROP 1 #define SELABEL_X_EXT 2 #define SELABEL_X_CLIENT 3 #define SELABEL_X_EVENT 4 #define SELABEL_X_SELN 5 #define SELABEL_X_POLYPROP 6 #define SELABEL_X_POLYSELN 7 /* DB backend */ #define SELABEL_DB_DATABASE 1 #define SELABEL_DB_SCHEMA 2 #define SELABEL_DB_TABLE 3 #define SELABEL_DB_COLUMN 4 #define SELABEL_DB_SEQUENCE 5 #define SELABEL_DB_VIEW 6 #define SELABEL_DB_PROCEDURE 7 #define SELABEL_DB_BLOB 8 #define SELABEL_DB_TUPLE 9 #define SELABEL_DB_LANGUAGE 10 #ifdef __cplusplus } #endif #endif /* _SELABEL_H_ */ include/selinux/selinux.h0100644 0000000 0000000 00000023447 12763246076 014606 0ustar000000000 0000000 #ifndef _SELINUX_H_ #define _SELINUX_H_ #include #include #ifdef __cplusplus extern "C" { #endif /* Return 1 if we are running on a SELinux kernel, or 0 if not or -1 if we get an error. */ extern int is_selinux_enabled(void); /* Return 1 if we are running on a SELinux MLS kernel, or 0 otherwise. */ extern int is_selinux_mls_enabled(void); /* No longer used; here for compatibility with legacy callers. */ typedef char *security_context_t; /* Free the memory allocated for a context by any of the below get* calls. */ extern void freecon(char * con); /* Free the memory allocated for a context array by security_compute_user. */ extern void freeconary(char ** con); /* Wrappers for the /proc/pid/attr API. */ /* Get current context, and set *con to refer to it. Caller must free via freecon. */ extern int getcon(char ** con); /* Set the current security context to con. Note that use of this function requires that the entire application be trusted to maintain any desired separation between the old and new security contexts, unlike exec-based transitions performed via setexeccon. When possible, decompose your application and use setexeccon()+execve() instead. Note that the application may lose access to its open descriptors as a result of a setcon() unless policy allows it to use descriptors opened by the old context. */ extern int setcon(const char * con); /* Get context of process identified by pid, and set *con to refer to it. Caller must free via freecon. */ extern int getpidcon(pid_t pid, char ** con); /* Get previous context (prior to last exec), and set *con to refer to it. Caller must free via freecon. */ extern int getprevcon(char ** con); /* Get exec context, and set *con to refer to it. Sets *con to NULL if no exec context has been set, i.e. using default. If non-NULL, caller must free via freecon. */ extern int getexeccon(char ** con); /* Set exec security context for the next execve. Call with NULL if you want to reset to the default. */ extern int setexeccon(const char * con); /* Get fscreate context, and set *con to refer to it. Sets *con to NULL if no fs create context has been set, i.e. using default. If non-NULL, caller must free via freecon. */ extern int getfscreatecon(char ** con); /* Set the fscreate security context for subsequent file creations. Call with NULL if you want to reset to the default. */ extern int setfscreatecon(const char * context); /* Get keycreate context, and set *con to refer to it. Sets *con to NULL if no key create context has been set, i.e. using default. If non-NULL, caller must free via freecon. */ extern int getkeycreatecon(char ** con); /* Set the keycreate security context for subsequent key creations. Call with NULL if you want to reset to the default. */ extern int setkeycreatecon(const char * context); /* Get sockcreate context, and set *con to refer to it. Sets *con to NULL if no socket create context has been set, i.e. using default. If non-NULL, caller must free via freecon. */ extern int getsockcreatecon(char ** con); /* Set the sockcreate security context for subsequent socket creations. Call with NULL if you want to reset to the default. */ extern int setsockcreatecon(const char * context); /* Wrappers for the xattr API. */ /* Get file context, and set *con to refer to it. Caller must free via freecon. */ extern int getfilecon(const char *path, char ** con); extern int lgetfilecon(const char *path, char ** con); extern int fgetfilecon(int fd, char ** con); /* Set file context */ extern int setfilecon(const char *path, const char *con); extern int lsetfilecon(const char *path, const char *con); extern int fsetfilecon(int fd, const char *con); /* Wrappers for the socket API */ /* Get context of peer socket, and set *con to refer to it. Caller must free via freecon. */ extern int getpeercon(int fd, char ** con); /* Wrappers for the selinuxfs (policy) API. */ typedef unsigned int access_vector_t; typedef unsigned short security_class_t; struct av_decision { access_vector_t allowed; access_vector_t decided; access_vector_t auditallow; access_vector_t auditdeny; unsigned int seqno; unsigned int flags; }; /* Definitions of av_decision.flags */ #define SELINUX_AVD_FLAGS_PERMISSIVE 0x0001 /* Structure for passing options, used by AVC and label subsystems */ struct selinux_opt { int type; const char *value; }; /* Callback facilities */ union selinux_callback { /* log the printf-style format and arguments, with the type code indicating the type of message */ int #ifdef __GNUC__ __attribute__ ((format(printf, 2, 3))) #endif (*func_log) (int type, const char *fmt, ...); /* store a string representation of auditdata (corresponding to the given security class) into msgbuf. */ int (*func_audit) (void *auditdata, security_class_t cls, char *msgbuf, size_t msgbufsize); /* validate the supplied context, modifying if necessary */ int (*func_validate) (char **ctx); /* netlink callback for setenforce message */ int (*func_setenforce) (int enforcing); /* netlink callback for policyload message */ int (*func_policyload) (int seqno); }; #define SELINUX_CB_LOG 0 #define SELINUX_CB_AUDIT 1 #define SELINUX_CB_VALIDATE 2 #define SELINUX_CB_SETENFORCE 3 #define SELINUX_CB_POLICYLOAD 4 extern union selinux_callback selinux_get_callback(int type); extern void selinux_set_callback(int type, union selinux_callback cb); /* Logging type codes, passed to the logging callback */ #define SELINUX_ERROR 0 #define SELINUX_WARNING 1 #define SELINUX_INFO 2 #define SELINUX_AVC 3 /* Compute an access decision. */ extern int security_compute_av(const char * scon, const char * tcon, security_class_t tclass, access_vector_t requested, struct av_decision *avd); /* Compute a labeling decision and set *newcon to refer to it. Caller must free via freecon. */ extern int security_compute_create(const char * scon, const char * tcon, security_class_t tclass, char ** newcon); /* Compute a relabeling decision and set *newcon to refer to it. Caller must free via freecon. */ extern int security_compute_relabel(const char * scon, const char * tcon, security_class_t tclass, char ** newcon); /* Compute a polyinstantiation member decision and set *newcon to refer to it. Caller must free via freecon. */ extern int security_compute_member(const char * scon, const char * tcon, security_class_t tclass, char ** newcon); /* Compute the set of reachable user contexts and set *con to refer to the NULL-terminated array of contexts. Caller must free via freeconary. */ extern int security_compute_user(const char * scon, const char *username, char *** con); /* Load a policy configuration. */ extern int security_load_policy(void *data, size_t len); /* Get the context of an initial kernel security identifier by name. Caller must free via freecon */ extern int security_get_initial_context(const char *name, char ** con); /* Translate boolean strict to name value pair. */ typedef struct { const char *name; int value; } SELboolean; /* save a list of booleans in a single transaction. */ extern int security_set_boolean_list(size_t boolcnt, SELboolean * const boollist, int permanent); /* Check the validity of a security context. */ extern int security_check_context(const char * con); /* Canonicalize a security context. */ extern int security_canonicalize_context(const char * con, char ** canoncon); /* Get the enforce flag value. */ extern int security_getenforce(void); /* Set the enforce flag value. */ extern int security_setenforce(int value); /* Get the behavior for undefined classes/permissions */ extern int security_deny_unknown(void); /* Disable SELinux at runtime (must be done prior to initial policy load). */ extern int security_disable(void); /* Get the policy version number. */ extern int security_policyvers(void); /* Get the boolean names */ extern int security_get_boolean_names(char ***names, int *len); /* Get the pending value for the boolean */ extern int security_get_boolean_pending(const char *name); /* Get the active value for the boolean */ extern int security_get_boolean_active(const char *name); /* Set the pending value for the boolean */ extern int security_set_boolean(const char *name, int value); /* Commit the pending values for the booleans */ extern int security_commit_booleans(void); /* Userspace class mapping support */ struct security_class_mapping { const char *name; const char *perms[sizeof(access_vector_t) * 8 + 1]; }; extern int selinux_set_mapping(struct security_class_mapping *map); /* Common helpers */ /* Convert between security class values and string names */ extern security_class_t string_to_security_class(const char *name); extern const char *security_class_to_string(security_class_t cls); /* Convert between individual access vector permissions and string names */ extern const char *security_av_perm_to_string(security_class_t tclass, access_vector_t perm); extern access_vector_t string_to_av_perm(security_class_t tclass, const char *name); /* Returns an access vector in a string representation. User must free the * returned string via free(). */ extern int security_av_string(security_class_t tclass, access_vector_t av, char **result); /* Check permissions and perform appropriate auditing. */ extern int selinux_check_access(const char * scon, const char * tcon, const char *tclass, const char *perm, void *aux); /* Set the path to the selinuxfs mount point explicitly. Normally, this is determined automatically during libselinux initialization, but this is not always possible, e.g. for /sbin/init which performs the initial mount of selinuxfs. */ void set_selinuxmnt(const char *mnt); #ifdef __cplusplus } #endif #endif src/0040755 0000000 0000000 00000000000 12763246076 010414 5ustar000000000 0000000 src/android.c0100644 0000000 0000000 00000125364 12763246076 012210 0ustar000000000 0000000 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "policy.h" #include "callbacks.h" #include "selinux_internal.h" #include "label_internal.h" #include #include #include #include #include #include #define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_ #include /* * XXX Where should this configuration file be located? * Needs to be accessible by zygote and installd when * setting credentials for app processes and setting permissions * on app data directories. */ static char const * const seapp_contexts_file[] = { "/seapp_contexts", "/data/security/current/seapp_contexts", NULL }; static const struct selinux_opt seopts[] = { { SELABEL_OPT_PATH, "/file_contexts.bin" }, { SELABEL_OPT_PATH, "/data/security/current/file_contexts.bin" }, { 0, NULL } }; static const char *const sepolicy_file[] = { "/sepolicy", "/data/security/current/sepolicy", NULL }; static const struct selinux_opt seopts_prop[] = { { SELABEL_OPT_PATH, "/property_contexts" }, { SELABEL_OPT_PATH, "/data/security/current/property_contexts" }, { 0, NULL } }; static const struct selinux_opt seopts_service[] = { { SELABEL_OPT_PATH, "/service_contexts" }, { SELABEL_OPT_PATH, "/data/security/current/service_contexts" }, { 0, NULL } }; enum levelFrom { LEVELFROM_NONE, LEVELFROM_APP, LEVELFROM_USER, LEVELFROM_ALL }; #define POLICY_OVERRIDE_VERSION "/data/security/current/selinux_version" #define POLICY_BASE_VERSION "/selinux_version" static int policy_index = 0; static void set_policy_index(void) { int fd_base = -1, fd_override = -1; struct stat sb_base; struct stat sb_override; void *map_base, *map_override; policy_index = 0; fd_base = open(POLICY_BASE_VERSION, O_RDONLY | O_NOFOLLOW); if (fd_base < 0) return; if (fstat(fd_base, &sb_base) < 0) goto close_base; fd_override = open(POLICY_OVERRIDE_VERSION, O_RDONLY | O_NOFOLLOW); if (fd_override < 0) goto close_base; if (fstat(fd_override, &sb_override) < 0) goto close_override; if (sb_base.st_size != sb_override.st_size) goto close_override; map_base = mmap(NULL, sb_base.st_size, PROT_READ, MAP_PRIVATE, fd_base, 0); if (map_base == MAP_FAILED) goto close_override; map_override = mmap(NULL, sb_override.st_size, PROT_READ, MAP_PRIVATE, fd_override, 0); if (map_override == MAP_FAILED) goto unmap_base; if (memcmp(map_base, map_override, sb_base.st_size) != 0) goto unmap_override; if (access(sepolicy_file[1], R_OK) != 0) goto unmap_override; if (access(seopts[1].value, R_OK) != 0) goto unmap_override; if (access(seopts_prop[1].value, R_OK) != 0) goto unmap_override; if (access(seopts_service[1].value, R_OK) != 0) goto unmap_override; if (access(seapp_contexts_file[1], R_OK) != 0) goto unmap_override; policy_index = 1; unmap_override: munmap(map_override, sb_override.st_size); unmap_base: munmap(map_base, sb_base.st_size); close_override: close(fd_override); close_base: close(fd_base); return; } #if DEBUG static char const * const levelFromName[] = { "none", "app", "user", "all" }; #endif struct prefix_str { size_t len; char *str; char is_prefix; }; static void free_prefix_str(struct prefix_str *p) { if (!p) return; free(p->str); } struct seapp_context { /* input selectors */ bool isSystemServer; bool isAutoPlayAppSet; bool isAutoPlayApp; bool isOwnerSet; bool isOwner; struct prefix_str user; char *seinfo; struct prefix_str name; struct prefix_str path; bool isPrivAppSet; bool isPrivApp; /* outputs */ char *domain; char *type; char *level; enum levelFrom levelFrom; }; static void free_seapp_context(struct seapp_context *s) { if (!s) return; free_prefix_str(&s->user); free(s->seinfo); free_prefix_str(&s->name); free_prefix_str(&s->path); free(s->domain); free(s->type); free(s->level); } static bool seapp_contexts_dup = false; static int seapp_context_cmp(const void *A, const void *B) { const struct seapp_context *const *sp1 = (const struct seapp_context *const *) A; const struct seapp_context *const *sp2 = (const struct seapp_context *const *) B; const struct seapp_context *s1 = *sp1, *s2 = *sp2; bool dup; /* Give precedence to isSystemServer=true. */ if (s1->isSystemServer != s2->isSystemServer) return (s1->isSystemServer ? -1 : 1); /* Give precedence to a specified isAutoPlayApp= over an * unspecified isAutoPlayApp=. */ if (s1->isAutoPlayAppSet != s2->isAutoPlayAppSet) return (s1->isAutoPlayAppSet ? -1 : 1); /* Give precedence to a specified isOwner= over an unspecified isOwner=. */ if (s1->isOwnerSet != s2->isOwnerSet) return (s1->isOwnerSet ? -1 : 1); /* Give precedence to a specified user= over an unspecified user=. */ if (s1->user.str && !s2->user.str) return -1; if (!s1->user.str && s2->user.str) return 1; if (s1->user.str) { /* Give precedence to a fixed user= string over a prefix. */ if (s1->user.is_prefix != s2->user.is_prefix) return (s2->user.is_prefix ? -1 : 1); /* Give precedence to a longer prefix over a shorter prefix. */ if (s1->user.is_prefix && s1->user.len != s2->user.len) return (s1->user.len > s2->user.len) ? -1 : 1; } /* Give precedence to a specified seinfo= over an unspecified seinfo=. */ if (s1->seinfo && !s2->seinfo) return -1; if (!s1->seinfo && s2->seinfo) return 1; /* Give precedence to a specified name= over an unspecified name=. */ if (s1->name.str && !s2->name.str) return -1; if (!s1->name.str && s2->name.str) return 1; if (s1->name.str) { /* Give precedence to a fixed name= string over a prefix. */ if (s1->name.is_prefix != s2->name.is_prefix) return (s2->name.is_prefix ? -1 : 1); /* Give precedence to a longer prefix over a shorter prefix. */ if (s1->name.is_prefix && s1->name.len != s2->name.len) return (s1->name.len > s2->name.len) ? -1 : 1; } /* Give precedence to a specified path= over an unspecified path=. */ if (s1->path.str && !s2->path.str) return -1; if (!s1->path.str && s2->path.str) return 1; if (s1->path.str) { /* Give precedence to a fixed path= string over a prefix. */ if (s1->path.is_prefix != s2->path.is_prefix) return (s2->path.is_prefix ? -1 : 1); /* Give precedence to a longer prefix over a shorter prefix. */ if (s1->path.is_prefix && s1->path.len != s2->path.len) return (s1->path.len > s2->path.len) ? -1 : 1; } /* Give precedence to a specified isPrivApp= over an unspecified isPrivApp=. */ if (s1->isPrivAppSet != s2->isPrivAppSet) return (s1->isPrivAppSet ? -1 : 1); /* * Check for a duplicated entry on the input selectors. * We already compared isSystemServer, isOwnerSet, and isOwner above. * We also have already checked that both entries specify the same * string fields, so if s1 has a non-NULL string, then so does s2. */ dup = (!s1->user.str || !strcmp(s1->user.str, s2->user.str)) && (!s1->seinfo || !strcmp(s1->seinfo, s2->seinfo)) && (!s1->name.str || !strcmp(s1->name.str, s2->name.str)) && (!s1->path.str || !strcmp(s1->path.str, s2->path.str)); if (dup) { seapp_contexts_dup = true; selinux_log(SELINUX_ERROR, "seapp_contexts: Duplicated entry\n"); if (s1->user.str) selinux_log(SELINUX_ERROR, " user=%s\n", s1->user.str); if (s1->seinfo) selinux_log(SELINUX_ERROR, " seinfo=%s\n", s1->seinfo); if (s1->name.str) selinux_log(SELINUX_ERROR, " name=%s\n", s1->name.str); if (s1->path.str) selinux_log(SELINUX_ERROR, " path=%s\n", s1->path.str); } /* Anything else has equal precedence. */ return 0; } static struct seapp_context **seapp_contexts = NULL; static int nspec = 0; static void free_seapp_contexts(void) { int n; if (!seapp_contexts) return; for (n = 0; n < nspec; n++) free_seapp_context(seapp_contexts[n]); free(seapp_contexts); seapp_contexts = NULL; nspec = 0; } int selinux_android_seapp_context_reload(void) { FILE *fp = NULL; char line_buf[BUFSIZ]; char *token; unsigned lineno; struct seapp_context *cur; char *p, *name = NULL, *value = NULL, *saveptr; size_t len; int n, ret; set_policy_index(); fp = fopen(seapp_contexts_file[policy_index], "r"); if (!fp) { selinux_log(SELINUX_ERROR, "%s: could not open any seapp_contexts file", __FUNCTION__); return -1; } free_seapp_contexts(); nspec = 0; while (fgets(line_buf, sizeof line_buf - 1, fp)) { p = line_buf; while (isspace(*p)) p++; if (*p == '#' || *p == 0) continue; nspec++; } seapp_contexts = (struct seapp_context **) calloc(nspec, sizeof(struct seapp_context *)); if (!seapp_contexts) goto oom; rewind(fp); nspec = 0; lineno = 1; while (fgets(line_buf, sizeof line_buf - 1, fp)) { len = strlen(line_buf); if (line_buf[len - 1] == '\n') line_buf[len - 1] = 0; p = line_buf; while (isspace(*p)) p++; if (*p == '#' || *p == 0) continue; cur = (struct seapp_context *) calloc(1, sizeof(struct seapp_context)); if (!cur) goto oom; token = strtok_r(p, " \t", &saveptr); if (!token) { free_seapp_context(cur); goto err; } while (1) { name = token; value = strchr(name, '='); if (!value) { free_seapp_context(cur); goto err; } *value++ = 0; if (!strcasecmp(name, "isSystemServer")) { if (!strcasecmp(value, "true")) cur->isSystemServer = true; else if (!strcasecmp(value, "false")) cur->isSystemServer = false; else { free_seapp_context(cur); goto err; } } else if (!strcasecmp(name, "isAutoPlayApp")) { cur->isAutoPlayAppSet = true; if (!strcasecmp(value, "true")) cur->isAutoPlayApp = true; else if (!strcasecmp(value, "false")) cur->isAutoPlayApp = false; else { free_seapp_context(cur); goto err; } } else if (!strcasecmp(name, "isOwner")) { cur->isOwnerSet = true; if (!strcasecmp(value, "true")) cur->isOwner = true; else if (!strcasecmp(value, "false")) cur->isOwner = false; else { free_seapp_context(cur); goto err; } } else if (!strcasecmp(name, "user")) { if (cur->user.str) { free_seapp_context(cur); goto err; } cur->user.str = strdup(value); if (!cur->user.str) { free_seapp_context(cur); goto oom; } cur->user.len = strlen(cur->user.str); if (cur->user.str[cur->user.len-1] == '*') cur->user.is_prefix = 1; } else if (!strcasecmp(name, "seinfo")) { if (cur->seinfo) { free_seapp_context(cur); goto err; } cur->seinfo = strdup(value); if (!cur->seinfo) { free_seapp_context(cur); goto oom; } if (strstr(value, ":")) { free_seapp_context(cur); goto err; } } else if (!strcasecmp(name, "name")) { if (cur->name.str) { free_seapp_context(cur); goto err; } cur->name.str = strdup(value); if (!cur->name.str) { free_seapp_context(cur); goto oom; } cur->name.len = strlen(cur->name.str); if (cur->name.str[cur->name.len-1] == '*') cur->name.is_prefix = 1; } else if (!strcasecmp(name, "domain")) { if (cur->domain) { free_seapp_context(cur); goto err; } cur->domain = strdup(value); if (!cur->domain) { free_seapp_context(cur); goto oom; } } else if (!strcasecmp(name, "type")) { if (cur->type) { free_seapp_context(cur); goto err; } cur->type = strdup(value); if (!cur->type) { free_seapp_context(cur); goto oom; } } else if (!strcasecmp(name, "levelFromUid")) { if (cur->levelFrom) { free_seapp_context(cur); goto err; } if (!strcasecmp(value, "true")) cur->levelFrom = LEVELFROM_APP; else if (!strcasecmp(value, "false")) cur->levelFrom = LEVELFROM_NONE; else { free_seapp_context(cur); goto err; } } else if (!strcasecmp(name, "levelFrom")) { if (cur->levelFrom) { free_seapp_context(cur); goto err; } if (!strcasecmp(value, "none")) cur->levelFrom = LEVELFROM_NONE; else if (!strcasecmp(value, "app")) cur->levelFrom = LEVELFROM_APP; else if (!strcasecmp(value, "user")) cur->levelFrom = LEVELFROM_USER; else if (!strcasecmp(value, "all")) cur->levelFrom = LEVELFROM_ALL; else { free_seapp_context(cur); goto err; } } else if (!strcasecmp(name, "level")) { if (cur->level) { free_seapp_context(cur); goto err; } cur->level = strdup(value); if (!cur->level) { free_seapp_context(cur); goto oom; } } else if (!strcasecmp(name, "path")) { if (cur->path.str) { free_seapp_context(cur); goto err; } cur->path.str = strdup(value); if (!cur->path.str) { free_seapp_context(cur); goto oom; } cur->path.len = strlen(cur->path.str); if (cur->path.str[cur->path.len-1] == '*') cur->path.is_prefix = 1; } else if (!strcasecmp(name, "isPrivApp")) { cur->isPrivAppSet = true; if (!strcasecmp(value, "true")) cur->isPrivApp = true; else if (!strcasecmp(value, "false")) cur->isPrivApp = false; else { free_seapp_context(cur); goto err; } } else { free_seapp_context(cur); goto err; } token = strtok_r(NULL, " \t", &saveptr); if (!token) break; } if (cur->name.str && (!cur->seinfo || !strcmp(cur->seinfo, "default"))) { selinux_log(SELINUX_ERROR, "%s: No specific seinfo value specified with name=\"%s\", on line %u: insecure configuration!\n", seapp_contexts_file[policy_index], cur->name.str, lineno); free_seapp_context(cur); goto err; } seapp_contexts[nspec] = cur; nspec++; lineno++; } qsort(seapp_contexts, nspec, sizeof(struct seapp_context *), seapp_context_cmp); if (seapp_contexts_dup) goto err; #if DEBUG { int i; for (i = 0; i < nspec; i++) { cur = seapp_contexts[i]; selinux_log(SELINUX_INFO, "%s: isSystemServer=%s isAutoPlayApp=%s isOwner=%s user=%s seinfo=%s " "name=%s path=%s isPrivApp=%s -> domain=%s type=%s level=%s levelFrom=%s", __FUNCTION__, cur->isSystemServer ? "true" : "false", cur->isAutoPlayAppSet ? (cur->isAutoPlayApp ? "true" : "false") : "null", cur->isOwnerSet ? (cur->isOwner ? "true" : "false") : "null", cur->user.str, cur->seinfo, cur->name.str, cur->path.str, cur->isPrivAppSet ? (cur->isPrivApp ? "true" : "false") : "null", cur->domain, cur->type, cur->level, levelFromName[cur->levelFrom]); } } #endif ret = 0; out: fclose(fp); return ret; err: selinux_log(SELINUX_ERROR, "%s: Invalid entry on line %u\n", seapp_contexts_file[policy_index], lineno); free_seapp_contexts(); ret = -1; goto out; oom: selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __FUNCTION__); free_seapp_contexts(); ret = -1; goto out; } static void seapp_context_init(void) { selinux_android_seapp_context_reload(); } static pthread_once_t once = PTHREAD_ONCE_INIT; /* * Max id that can be mapped to category set uniquely * using the current scheme. */ #define CAT_MAPPING_MAX_ID (0x1<<16) enum seapp_kind { SEAPP_TYPE, SEAPP_DOMAIN }; #define PRIVILEGED_APP_STR ":privapp" #define AUTOPLAY_APP_STR ":autoplayapp" static int seinfo_parse(char *dest, const char *src, size_t size) { size_t len; char *p; if ((p = strchr(src, ':')) != NULL) len = p - src; else len = strlen(src); if (len > size - 1) return -1; strncpy(dest, src, len); dest[len] = '\0'; return 0; } static int seapp_context_lookup(enum seapp_kind kind, uid_t uid, bool isSystemServer, const char *seinfo, const char *pkgname, const char *path, context_t ctx) { bool isOwner; const char *username = NULL; struct seapp_context *cur = NULL; int i; size_t n; uid_t userid; uid_t appid; bool isPrivApp = false; bool isAutoPlayApp = false; char parsedseinfo[BUFSIZ]; __selinux_once(once, seapp_context_init); if (seinfo) { if (seinfo_parse(parsedseinfo, seinfo, BUFSIZ)) goto err; isPrivApp = strstr(seinfo, PRIVILEGED_APP_STR) ? true : false; isAutoPlayApp = strstr(seinfo, AUTOPLAY_APP_STR) ? true : false; seinfo = parsedseinfo; } userid = uid / AID_USER; isOwner = (userid == 0); appid = uid % AID_USER; if (appid < AID_APP) { for (n = 0; n < android_id_count; n++) { if (android_ids[n].aid == appid) { username = android_ids[n].name; break; } } if (!username) goto err; } else if (appid < AID_ISOLATED_START) { username = "_app"; appid -= AID_APP; } else { username = "_isolated"; appid -= AID_ISOLATED_START; } if (appid >= CAT_MAPPING_MAX_ID || userid >= CAT_MAPPING_MAX_ID) goto err; for (i = 0; i < nspec; i++) { cur = seapp_contexts[i]; if (cur->isSystemServer != isSystemServer) continue; if (cur->isAutoPlayAppSet && cur->isAutoPlayApp != isAutoPlayApp) continue; if (cur->isOwnerSet && cur->isOwner != isOwner) continue; if (cur->user.str) { if (cur->user.is_prefix) { if (strncasecmp(username, cur->user.str, cur->user.len-1)) continue; } else { if (strcasecmp(username, cur->user.str)) continue; } } if (cur->seinfo) { if (!seinfo || strcasecmp(seinfo, cur->seinfo)) continue; } if (cur->name.str) { if(!pkgname) continue; if (cur->name.is_prefix) { if (strncasecmp(pkgname, cur->name.str, cur->name.len-1)) continue; } else { if (strcasecmp(pkgname, cur->name.str)) continue; } } if (cur->isPrivAppSet && cur->isPrivApp != isPrivApp) continue; if (cur->path.str) { if (!path) continue; if (cur->path.is_prefix) { if (strncmp(path, cur->path.str, cur->path.len-1)) continue; } else { if (strcmp(path, cur->path.str)) continue; } } if (kind == SEAPP_TYPE && !cur->type) continue; else if (kind == SEAPP_DOMAIN && !cur->domain) continue; if (kind == SEAPP_TYPE) { if (context_type_set(ctx, cur->type)) goto oom; } else if (kind == SEAPP_DOMAIN) { if (context_type_set(ctx, cur->domain)) goto oom; } if (cur->levelFrom != LEVELFROM_NONE) { char level[255]; switch (cur->levelFrom) { case LEVELFROM_APP: snprintf(level, sizeof level, "s0:c%u,c%u", appid & 0xff, 256 + (appid>>8 & 0xff)); break; case LEVELFROM_USER: snprintf(level, sizeof level, "s0:c%u,c%u", 512 + (userid & 0xff), 768 + (userid>>8 & 0xff)); break; case LEVELFROM_ALL: snprintf(level, sizeof level, "s0:c%u,c%u,c%u,c%u", appid & 0xff, 256 + (appid>>8 & 0xff), 512 + (userid & 0xff), 768 + (userid>>8 & 0xff)); break; default: goto err; } if (context_range_set(ctx, level)) goto oom; } else if (cur->level) { if (context_range_set(ctx, cur->level)) goto oom; } break; } if (kind == SEAPP_DOMAIN && i == nspec) { /* * No match. * Fail to prevent staying in the zygote's context. */ selinux_log(SELINUX_ERROR, "%s: No match for app with uid %d, seinfo %s, name %s\n", __FUNCTION__, uid, seinfo, pkgname); if (security_getenforce() == 1) goto err; } return 0; err: return -1; oom: return -2; } int selinux_android_setfilecon(const char *pkgdir, const char *pkgname, const char *seinfo, uid_t uid) { char *orig_ctx_str = NULL; char *ctx_str = NULL; context_t ctx = NULL; int rc = -1; if (is_selinux_enabled() <= 0) return 0; rc = getfilecon(pkgdir, &ctx_str); if (rc < 0) goto err; ctx = context_new(ctx_str); orig_ctx_str = ctx_str; if (!ctx) goto oom; rc = seapp_context_lookup(SEAPP_TYPE, uid, 0, seinfo, pkgname, NULL, ctx); if (rc == -1) goto err; else if (rc == -2) goto oom; ctx_str = context_str(ctx); if (!ctx_str) goto oom; rc = security_check_context(ctx_str); if (rc < 0) goto err; if (strcmp(ctx_str, orig_ctx_str)) { rc = setfilecon(pkgdir, ctx_str); if (rc < 0) goto err; } rc = 0; out: freecon(orig_ctx_str); context_free(ctx); return rc; err: selinux_log(SELINUX_ERROR, "%s: Error setting context for pkgdir %s, uid %d: %s\n", __FUNCTION__, pkgdir, uid, strerror(errno)); rc = -1; goto out; oom: selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __FUNCTION__); rc = -1; goto out; } int selinux_android_setcon(const char *con) { int ret = setcon(con); if (ret) return ret; /* System properties must be reinitialized after setcon() otherwise the previous property files will be leaked since mmap()'ed regions are not closed as a result of setcon(). */ return __system_properties_init(); } int selinux_android_setcontext(uid_t uid, bool isSystemServer, const char *seinfo, const char *pkgname) { char *orig_ctx_str = NULL, *ctx_str; context_t ctx = NULL; int rc = -1; if (is_selinux_enabled() <= 0) return 0; rc = getcon(&ctx_str); if (rc) goto err; ctx = context_new(ctx_str); orig_ctx_str = ctx_str; if (!ctx) goto oom; rc = seapp_context_lookup(SEAPP_DOMAIN, uid, isSystemServer, seinfo, pkgname, NULL, ctx); if (rc == -1) goto err; else if (rc == -2) goto oom; ctx_str = context_str(ctx); if (!ctx_str) goto oom; rc = security_check_context(ctx_str); if (rc < 0) goto err; if (strcmp(ctx_str, orig_ctx_str)) { rc = selinux_android_setcon(ctx_str); if (rc < 0) goto err; } rc = 0; out: freecon(orig_ctx_str); context_free(ctx); avc_netlink_close(); return rc; err: if (isSystemServer) selinux_log(SELINUX_ERROR, "%s: Error setting context for system server: %s\n", __FUNCTION__, strerror(errno)); else selinux_log(SELINUX_ERROR, "%s: Error setting context for app with uid %d, seinfo %s: %s\n", __FUNCTION__, uid, seinfo, strerror(errno)); rc = -1; goto out; oom: selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __FUNCTION__); rc = -1; goto out; } static struct selabel_handle *fc_sehandle = NULL; #define FC_DIGEST_SIZE SHA_DIGEST_LENGTH static uint8_t fc_digest[FC_DIGEST_SIZE]; static bool compute_contexts_hash(const struct selinux_opt opts[], uint8_t c_digest[]) { int fd; struct stat sb; void *map; fd = open(opts[policy_index].value, O_CLOEXEC | O_RDONLY | O_NOFOLLOW); if (fd < 0) { selinux_log(SELINUX_ERROR, "SELinux: Could not open %s: %s\n", opts[policy_index].value, strerror(errno)); return false; } if (fstat(fd, &sb) < 0) { selinux_log(SELINUX_ERROR, "SELinux: Could not stat %s: %s\n", opts[policy_index].value, strerror(errno)); close(fd); return false; } map = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); if (map == MAP_FAILED) { selinux_log(SELINUX_ERROR, "SELinux: Could not map %s: %s\n", opts[policy_index].value, strerror(errno)); close(fd); return false; } SHA1(map, sb.st_size, c_digest); munmap(map, sb.st_size); close(fd); return true; } static void file_context_init(void) { if (!fc_sehandle) fc_sehandle = selinux_android_file_context_handle(); } static pthread_once_t fc_once = PTHREAD_ONCE_INIT; #define PKGTAB_SIZE 256 static struct pkg_info *pkgTab[PKGTAB_SIZE]; static unsigned int pkghash(const char *pkgname) { unsigned int h = 7; for (; *pkgname; pkgname++) { h = h * 31 + *pkgname; } return h & (PKGTAB_SIZE - 1); } static bool pkg_parse_callback(pkg_info *info, void *userdata) { (void) userdata; unsigned int hash = pkghash(info->name); if (pkgTab[hash]) info->private_data = pkgTab[hash]; pkgTab[hash] = info; return true; } static void package_info_init(void) { bool rc = packagelist_parse(pkg_parse_callback, NULL); if (!rc) { selinux_log(SELINUX_ERROR, "SELinux: Could NOT parse package list\n"); return; } #if DEBUG { unsigned int hash, buckets, entries, chainlen, longestchain; struct pkg_info *info = NULL; buckets = entries = longestchain = 0; for (hash = 0; hash < PKGTAB_SIZE; hash++) { if (pkgTab[hash]) { buckets++; chainlen = 0; for (info = pkgTab[hash]; info; info = (pkg_info *)info->private_data) { chainlen++; selinux_log(SELINUX_INFO, "%s: name=%s uid=%u debuggable=%s dataDir=%s seinfo=%s\n", __FUNCTION__, info->name, info->uid, info->debuggable ? "true" : "false", info->data_dir, info->seinfo); } entries += chainlen; if (longestchain < chainlen) longestchain = chainlen; } } selinux_log(SELINUX_INFO, "SELinux: %d pkg entries and %d/%d buckets used, longest chain %d\n", entries, buckets, PKGTAB_SIZE, longestchain); } #endif } static pthread_once_t pkg_once = PTHREAD_ONCE_INIT; struct pkg_info *package_info_lookup(const char *name) { struct pkg_info *info; unsigned int hash; __selinux_once(pkg_once, package_info_init); hash = pkghash(name); for (info = pkgTab[hash]; info; info = (pkg_info *)info->private_data) { if (!strcmp(name, info->name)) return info; } return NULL; } /* The path prefixes of package data directories. */ #define DATA_DATA_PATH "/data/data" #define DATA_USER_PATH "/data/user" #define DATA_USER_DE_PATH "/data/user_de" #define EXPAND_USER_PATH "/mnt/expand/\?\?\?\?\?\?\?\?-\?\?\?\?-\?\?\?\?-\?\?\?\?-\?\?\?\?\?\?\?\?\?\?\?\?/user" #define EXPAND_USER_DE_PATH "/mnt/expand/\?\?\?\?\?\?\?\?-\?\?\?\?-\?\?\?\?-\?\?\?\?-\?\?\?\?\?\?\?\?\?\?\?\?/user_de" #define DATA_DATA_PREFIX DATA_DATA_PATH "/" #define DATA_USER_PREFIX DATA_USER_PATH "/" #define DATA_USER_DE_PREFIX DATA_USER_DE_PATH "/" static int pkgdir_selabel_lookup(const char *pathname, const char *seinfo, uid_t uid, char **secontextp) { char *pkgname = NULL, *end = NULL; struct pkg_info *info = NULL; char *secontext = *secontextp; context_t ctx = NULL; int rc = 0; /* Skip directory prefix before package name. */ if (!strncmp(pathname, DATA_DATA_PREFIX, sizeof(DATA_DATA_PREFIX)-1)) { pathname += sizeof(DATA_DATA_PREFIX) - 1; } else if (!strncmp(pathname, DATA_USER_PREFIX, sizeof(DATA_USER_PREFIX)-1)) { pathname += sizeof(DATA_USER_PREFIX) - 1; while (isdigit(*pathname)) pathname++; if (*pathname == '/') pathname++; else return 0; } else if (!strncmp(pathname, DATA_USER_DE_PREFIX, sizeof(DATA_USER_DE_PREFIX)-1)) { pathname += sizeof(DATA_USER_DE_PREFIX) - 1; while (isdigit(*pathname)) pathname++; if (*pathname == '/') pathname++; else return 0; } else if (!fnmatch(EXPAND_USER_PATH, pathname, FNM_LEADING_DIR|FNM_PATHNAME)) { pathname += sizeof(EXPAND_USER_PATH); while (isdigit(*pathname)) pathname++; if (*pathname == '/') pathname++; else return 0; } else if (!fnmatch(EXPAND_USER_DE_PATH, pathname, FNM_LEADING_DIR|FNM_PATHNAME)) { pathname += sizeof(EXPAND_USER_DE_PATH); while (isdigit(*pathname)) pathname++; if (*pathname == '/') pathname++; else return 0; } else return 0; if (!(*pathname)) return 0; pkgname = strdup(pathname); if (!pkgname) return -1; for (end = pkgname; *end && *end != '/'; end++) ; pathname = end; if (*end) pathname++; *end = '\0'; if (!seinfo) { info = package_info_lookup(pkgname); if (!info) { selinux_log(SELINUX_WARNING, "SELinux: Could not look up information for package %s, cannot restorecon %s.\n", pkgname, pathname); free(pkgname); return -1; } } ctx = context_new(secontext); if (!ctx) goto err; rc = seapp_context_lookup(SEAPP_TYPE, info ? info->uid : uid, 0, info ? info->seinfo : seinfo, info ? info->name : pkgname, pathname, ctx); if (rc < 0) goto err; secontext = context_str(ctx); if (!secontext) goto err; if (!strcmp(secontext, *secontextp)) goto out; rc = security_check_context(secontext); if (rc < 0) goto err; freecon(*secontextp); *secontextp = strdup(secontext); if (!(*secontextp)) goto err; rc = 0; out: free(pkgname); context_free(ctx); return rc; err: selinux_log(SELINUX_ERROR, "%s: Error looking up context for path %s, pkgname %s, seinfo %s, uid %u: %s\n", __FUNCTION__, pathname, pkgname, info->seinfo, info->uid, strerror(errno)); rc = -1; goto out; } #define RESTORECON_LAST "security.restorecon_last" static int restorecon_sb(const char *pathname, const struct stat *sb, bool nochange, bool verbose, const char *seinfo, uid_t uid) { char *secontext = NULL; char *oldsecontext = NULL; int rc = 0; if (selabel_lookup(fc_sehandle, &secontext, pathname, sb->st_mode) < 0) return 0; /* no match, but not an error */ if (lgetfilecon(pathname, &oldsecontext) < 0) goto err; /* * For subdirectories of /data/data or /data/user, we ignore selabel_lookup() * and use pkgdir_selabel_lookup() instead. Files within those directories * have different labeling rules, based off of /seapp_contexts, and * installd is responsible for managing these labels instead of init. */ if (!strncmp(pathname, DATA_DATA_PREFIX, sizeof(DATA_DATA_PREFIX)-1) || !strncmp(pathname, DATA_USER_PREFIX, sizeof(DATA_USER_PREFIX)-1) || !strncmp(pathname, DATA_USER_DE_PREFIX, sizeof(DATA_USER_DE_PREFIX)-1) || !fnmatch(EXPAND_USER_PATH, pathname, FNM_LEADING_DIR|FNM_PATHNAME) || !fnmatch(EXPAND_USER_DE_PATH, pathname, FNM_LEADING_DIR|FNM_PATHNAME)) { if (pkgdir_selabel_lookup(pathname, seinfo, uid, &secontext) < 0) goto err; } if (strcmp(oldsecontext, secontext) != 0) { if (verbose) selinux_log(SELINUX_INFO, "SELinux: Relabeling %s from %s to %s.\n", pathname, oldsecontext, secontext); if (!nochange) { if (lsetfilecon(pathname, secontext) < 0) goto err; } } rc = 0; out: freecon(oldsecontext); freecon(secontext); return rc; err: selinux_log(SELINUX_ERROR, "SELinux: Could not set context for %s: %s\n", pathname, strerror(errno)); rc = -1; goto out; } #define SYS_PATH "/sys" #define SYS_PREFIX SYS_PATH "/" static int selinux_android_restorecon_common(const char* pathname_orig, const char *seinfo, uid_t uid, unsigned int flags) { bool nochange = (flags & SELINUX_ANDROID_RESTORECON_NOCHANGE) ? true : false; bool verbose = (flags & SELINUX_ANDROID_RESTORECON_VERBOSE) ? true : false; bool recurse = (flags & SELINUX_ANDROID_RESTORECON_RECURSE) ? true : false; bool force = (flags & SELINUX_ANDROID_RESTORECON_FORCE) ? true : false; bool datadata = (flags & SELINUX_ANDROID_RESTORECON_DATADATA) ? true : false; bool issys; bool setrestoreconlast = true; struct stat sb; struct statfs sfsb; FTS *fts; FTSENT *ftsent; char *pathname = NULL, *pathdnamer = NULL, *pathdname, *pathbname; char * paths[2] = { NULL , NULL }; int ftsflags = FTS_NOCHDIR | FTS_XDEV | FTS_PHYSICAL; int error, sverrno; char xattr_value[FC_DIGEST_SIZE]; ssize_t size; if (is_selinux_enabled() <= 0) return 0; __selinux_once(fc_once, file_context_init); if (!fc_sehandle) return 0; /* * Convert passed-in pathname to canonical pathname by resolving realpath of * containing dir, then appending last component name. */ pathbname = basename(pathname_orig); if (!strcmp(pathbname, "/") || !strcmp(pathbname, ".") || !strcmp(pathbname, "..")) { pathname = realpath(pathname_orig, NULL); if (!pathname) goto realpatherr; } else { pathdname = dirname(pathname_orig); pathdnamer = realpath(pathdname, NULL); if (!pathdnamer) goto realpatherr; if (!strcmp(pathdnamer, "/")) error = asprintf(&pathname, "/%s", pathbname); else error = asprintf(&pathname, "%s/%s", pathdnamer, pathbname); if (error < 0) goto oom; } paths[0] = pathname; issys = (!strcmp(pathname, SYS_PATH) || !strncmp(pathname, SYS_PREFIX, sizeof(SYS_PREFIX)-1)) ? true : false; if (!recurse) { if (lstat(pathname, &sb) < 0) { error = -1; goto cleanup; } error = restorecon_sb(pathname, &sb, nochange, verbose, seinfo, uid); goto cleanup; } /* * Ignore restorecon_last on /data/data or /data/user * since their labeling is based on seapp_contexts and seinfo * assignments rather than file_contexts and is managed by * installd rather than init. */ if (!strncmp(pathname, DATA_DATA_PREFIX, sizeof(DATA_DATA_PREFIX)-1) || !strncmp(pathname, DATA_USER_PREFIX, sizeof(DATA_USER_PREFIX)-1) || !strncmp(pathname, DATA_USER_DE_PREFIX, sizeof(DATA_USER_DE_PREFIX)-1) || !fnmatch(EXPAND_USER_PATH, pathname, FNM_LEADING_DIR|FNM_PATHNAME) || !fnmatch(EXPAND_USER_DE_PATH, pathname, FNM_LEADING_DIR|FNM_PATHNAME)) setrestoreconlast = false; /* Also ignore on /sys since it is regenerated on each boot regardless. */ if (issys) setrestoreconlast = false; /* Ignore files on in-memory filesystems */ if (statfs(pathname, &sfsb) == 0) { if (sfsb.f_type == RAMFS_MAGIC || sfsb.f_type == TMPFS_MAGIC) setrestoreconlast = false; } if (setrestoreconlast) { size = getxattr(pathname, RESTORECON_LAST, xattr_value, sizeof fc_digest); if (!force && size == sizeof fc_digest && memcmp(fc_digest, xattr_value, sizeof fc_digest) == 0) { selinux_log(SELINUX_INFO, "SELinux: Skipping restorecon_recursive(%s)\n", pathname); error = 0; goto cleanup; } } fts = fts_open(paths, ftsflags, NULL); if (!fts) { error = -1; goto cleanup; } error = 0; while ((ftsent = fts_read(fts)) != NULL) { switch (ftsent->fts_info) { case FTS_DC: selinux_log(SELINUX_ERROR, "SELinux: Directory cycle on %s.\n", ftsent->fts_path); errno = ELOOP; error = -1; goto out; case FTS_DP: continue; case FTS_DNR: selinux_log(SELINUX_ERROR, "SELinux: Could not read %s: %s.\n", ftsent->fts_path, strerror(errno)); fts_set(fts, ftsent, FTS_SKIP); continue; case FTS_NS: selinux_log(SELINUX_ERROR, "SELinux: Could not stat %s: %s.\n", ftsent->fts_path, strerror(errno)); fts_set(fts, ftsent, FTS_SKIP); continue; case FTS_ERR: selinux_log(SELINUX_ERROR, "SELinux: Error on %s: %s.\n", ftsent->fts_path, strerror(errno)); fts_set(fts, ftsent, FTS_SKIP); continue; case FTS_D: if (issys && !selabel_partial_match(fc_sehandle, ftsent->fts_path)) { fts_set(fts, ftsent, FTS_SKIP); continue; } if (!datadata && (!strcmp(ftsent->fts_path, DATA_DATA_PATH) || !strncmp(ftsent->fts_path, DATA_USER_PREFIX, sizeof(DATA_USER_PREFIX)-1) || !strncmp(ftsent->fts_path, DATA_USER_DE_PREFIX, sizeof(DATA_USER_DE_PREFIX)-1) || !fnmatch(EXPAND_USER_PATH, ftsent->fts_path, FNM_LEADING_DIR|FNM_PATHNAME) || !fnmatch(EXPAND_USER_DE_PATH, ftsent->fts_path, FNM_LEADING_DIR|FNM_PATHNAME))) { // Don't label anything below this directory. fts_set(fts, ftsent, FTS_SKIP); // but fall through and make sure we label the directory itself } /* fall through */ default: error |= restorecon_sb(ftsent->fts_path, ftsent->fts_statp, nochange, verbose, seinfo, uid); break; } } // Labeling successful. Mark the top level directory as completed. if (setrestoreconlast && !nochange && !error) setxattr(pathname, RESTORECON_LAST, fc_digest, sizeof fc_digest, 0); out: sverrno = errno; (void) fts_close(fts); errno = sverrno; cleanup: free(pathdnamer); free(pathname); return error; oom: sverrno = errno; selinux_log(SELINUX_ERROR, "%s: Out of memory\n", __FUNCTION__); errno = sverrno; error = -1; goto cleanup; realpatherr: sverrno = errno; selinux_log(SELINUX_ERROR, "SELinux: Could not get canonical path for %s restorecon: %s.\n", pathname_orig, strerror(errno)); errno = sverrno; error = -1; goto cleanup; } int selinux_android_restorecon(const char *file, unsigned int flags) { return selinux_android_restorecon_common(file, NULL, -1, flags); } int selinux_android_restorecon_pkgdir(const char *pkgdir, const char *seinfo, uid_t uid, unsigned int flags) { return selinux_android_restorecon_common(pkgdir, seinfo, uid, flags | SELINUX_ANDROID_RESTORECON_DATADATA); } struct selabel_handle* selinux_android_file_context_handle(void) { char *path = NULL; struct selabel_handle *sehandle; struct selinux_opt fc_opts[] = { { SELABEL_OPT_PATH, path }, { SELABEL_OPT_BASEONLY, (char *)1 } }; set_policy_index(); fc_opts[0].value = seopts[policy_index].value; sehandle = selabel_open(SELABEL_CTX_FILE, fc_opts, 2); if (!sehandle) { selinux_log(SELINUX_ERROR, "%s: Error getting file context handle (%s)\n", __FUNCTION__, strerror(errno)); return NULL; } if (!compute_contexts_hash(seopts, fc_digest)) { selabel_close(sehandle); return NULL; } selinux_log(SELINUX_INFO, "SELinux: Loaded file_contexts contexts from %s.\n", fc_opts[0].value); return sehandle; } struct selabel_handle* selinux_android_prop_context_handle(void) { struct selabel_handle* sehandle; set_policy_index(); sehandle = selabel_open(SELABEL_CTX_ANDROID_PROP, &seopts_prop[policy_index], 1); if (!sehandle) { selinux_log(SELINUX_ERROR, "%s: Error getting property context handle (%s)\n", __FUNCTION__, strerror(errno)); return NULL; } selinux_log(SELINUX_INFO, "SELinux: Loaded property_contexts from %s.\n", seopts_prop[policy_index].value); return sehandle; } struct selabel_handle* selinux_android_service_context_handle(void) { struct selabel_handle* sehandle; set_policy_index(); sehandle = selabel_open(SELABEL_CTX_ANDROID_PROP, &seopts_service[policy_index], 1); if (!sehandle) { selinux_log(SELINUX_ERROR, "%s: Error getting service context handle (%s)\n", __FUNCTION__, strerror(errno)); return NULL; } selinux_log(SELINUX_INFO, "SELinux: Loaded service_contexts from %s.\n", seopts_service[policy_index].value); return sehandle; } void selinux_android_set_sehandle(const struct selabel_handle *hndl) { fc_sehandle = (struct selabel_handle *) hndl; } static int selinux_android_load_policy_helper(bool reload) { int fd = -1, rc; struct stat sb; void *map = NULL; int old_policy_index = policy_index; /* * If reloading policy and there is no /data policy or * that /data policy has the wrong version and our prior * load was from the / policy, then just return. * There is no point in reloading policy from / a second time. */ set_policy_index(); if (reload && !policy_index && !old_policy_index) return 0; fd = open(sepolicy_file[policy_index], O_RDONLY | O_NOFOLLOW); if (fd < 0) { selinux_log(SELINUX_ERROR, "SELinux: Could not open sepolicy: %s\n", strerror(errno)); return -1; } if (fstat(fd, &sb) < 0) { selinux_log(SELINUX_ERROR, "SELinux: Could not stat %s: %s\n", sepolicy_file[policy_index], strerror(errno)); close(fd); return -1; } map = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); if (map == MAP_FAILED) { selinux_log(SELINUX_ERROR, "SELinux: Could not map %s: %s\n", sepolicy_file[policy_index], strerror(errno)); close(fd); return -1; } rc = security_load_policy(map, sb.st_size); if (rc < 0) { selinux_log(SELINUX_ERROR, "SELinux: Could not load policy: %s\n", strerror(errno)); munmap(map, sb.st_size); close(fd); return -1; } munmap(map, sb.st_size); close(fd); selinux_log(SELINUX_INFO, "SELinux: Loaded policy from %s\n", sepolicy_file[policy_index]); return 0; } int selinux_android_reload_policy(void) { return selinux_android_load_policy_helper(true); } int selinux_android_load_policy(void) { const char *mnt = SELINUXMNT; int rc; rc = mount(SELINUXFS, mnt, SELINUXFS, 0, NULL); if (rc < 0) { if (errno == ENODEV) { /* SELinux not enabled in kernel */ return -1; } if (errno == ENOENT) { /* Fall back to legacy mountpoint. */ mnt = OLDSELINUXMNT; rc = mkdir(mnt, 0755); if (rc == -1 && errno != EEXIST) { selinux_log(SELINUX_ERROR,"SELinux: Could not mkdir: %s\n", strerror(errno)); return -1; } rc = mount(SELINUXFS, mnt, SELINUXFS, 0, NULL); } } if (rc < 0) { selinux_log(SELINUX_ERROR,"SELinux: Could not mount selinuxfs: %s\n", strerror(errno)); return -1; } set_selinuxmnt(mnt); return selinux_android_load_policy_helper(false); } int selinux_log_callback(int type, const char *fmt, ...) { va_list ap; int priority; char *strp; switch(type) { case SELINUX_WARNING: priority = ANDROID_LOG_WARN; break; case SELINUX_INFO: priority = ANDROID_LOG_INFO; break; default: priority = ANDROID_LOG_ERROR; break; } va_start(ap, fmt); if (vasprintf(&strp, fmt, ap) != -1) { LOG_PRI(priority, "SELinux", "%s", strp); LOG_EVENT_STRING(AUDITD_LOG_TAG, strp); free(strp); } va_end(ap); return 0; } src/avc.c0100644 0000000 0000000 00000063034 12763246076 011334 0ustar000000000 0000000 /* * Implementation of the userspace access vector cache (AVC). * * Author : Eamon Walsh * * Derived from the kernel AVC implementation by * Stephen Smalley and * James Morris . */ #include #include "selinux_internal.h" #include #include "avc_sidtab.h" #include "avc_internal.h" #define AVC_CACHE_SLOTS 512 #define AVC_CACHE_MAXNODES 410 struct avc_entry { security_id_t ssid; security_id_t tsid; security_class_t tclass; struct av_decision avd; security_id_t create_sid; int used; /* used recently */ }; struct avc_node { struct avc_entry ae; struct avc_node *next; }; struct avc_cache { struct avc_node *slots[AVC_CACHE_SLOTS]; uint32_t lru_hint; /* LRU hint for reclaim scan */ uint32_t active_nodes; uint32_t latest_notif; /* latest revocation notification */ }; struct avc_callback_node { int (*callback) (uint32_t event, security_id_t ssid, security_id_t tsid, security_class_t tclass, access_vector_t perms, access_vector_t * out_retained); uint32_t events; security_id_t ssid; security_id_t tsid; security_class_t tclass; access_vector_t perms; struct avc_callback_node *next; }; static void *avc_netlink_thread = NULL; static void *avc_lock = NULL; static void *avc_log_lock = NULL; static struct avc_node *avc_node_freelist = NULL; static struct avc_cache avc_cache; static char *avc_audit_buf = NULL; static struct avc_cache_stats cache_stats; static struct avc_callback_node *avc_callbacks = NULL; static struct sidtab avc_sidtab; static inline int avc_hash(security_id_t ssid, security_id_t tsid, security_class_t tclass) { return ((uintptr_t) ssid ^ ((uintptr_t) tsid << 2) ^ tclass) & (AVC_CACHE_SLOTS - 1); } int avc_context_to_sid(const char * ctx, security_id_t * sid) { int rc; /* avc_init needs to be called before this function */ assert(avc_running); avc_get_lock(avc_lock); rc = sidtab_context_to_sid(&avc_sidtab, ctx, sid); avc_release_lock(avc_lock); return rc; } int avc_sid_to_context(security_id_t sid, char ** ctx) { int rc; *ctx = NULL; avc_get_lock(avc_lock); *ctx = strdup(sid->ctx); /* caller must free via freecon */ rc = *ctx ? 0 : -1; avc_release_lock(avc_lock); return rc; } int avc_get_initial_sid(const char * name, security_id_t * sid) { int rc; char * con; rc = security_get_initial_context(name, &con); if (rc < 0) return rc; rc = avc_context_to_sid(con, sid); freecon(con); return rc; } int avc_open(struct selinux_opt *opts, unsigned nopts) { avc_setenforce = 0; while (nopts--) switch(opts[nopts].type) { case AVC_OPT_SETENFORCE: avc_setenforce = 1; avc_enforcing = !!opts[nopts].value; break; } return avc_init("avc", NULL, NULL, NULL, NULL); } int avc_init(const char *prefix, const struct avc_memory_callback *mem_cb, const struct avc_log_callback *log_cb, const struct avc_thread_callback *thread_cb, const struct avc_lock_callback *lock_cb) { struct avc_node *new; int i, rc = 0; if (avc_running) return 0; if (prefix) strncpy(avc_prefix, prefix, AVC_PREFIX_SIZE - 1); set_callbacks(mem_cb, log_cb, thread_cb, lock_cb); avc_lock = avc_alloc_lock(); avc_log_lock = avc_alloc_lock(); memset(&cache_stats, 0, sizeof(cache_stats)); for (i = 0; i < AVC_CACHE_SLOTS; i++) avc_cache.slots[i] = 0; avc_cache.lru_hint = 0; avc_cache.active_nodes = 0; avc_cache.latest_notif = 0; rc = sidtab_init(&avc_sidtab); if (rc) { avc_log(SELINUX_ERROR, "%s: unable to initialize SID table\n", avc_prefix); goto out; } avc_audit_buf = (char *)avc_malloc(AVC_AUDIT_BUFSIZE); if (!avc_audit_buf) { avc_log(SELINUX_ERROR, "%s: unable to allocate audit buffer\n", avc_prefix); rc = -1; goto out; } for (i = 0; i < AVC_CACHE_MAXNODES; i++) { new = avc_malloc(sizeof(*new)); if (!new) { avc_log(SELINUX_WARNING, "%s: warning: only got %d av entries\n", avc_prefix, i); break; } memset(new, 0, sizeof(*new)); new->next = avc_node_freelist; avc_node_freelist = new; } if (!avc_setenforce) { rc = security_getenforce(); if (rc < 0) { avc_log(SELINUX_ERROR, "%s: could not determine enforcing mode: %s\n", avc_prefix, strerror(errno)); goto out; } avc_enforcing = rc; } rc = avc_netlink_open(0); if (rc < 0) { avc_log(SELINUX_ERROR, "%s: can't open netlink socket: %d (%s)\n", avc_prefix, errno, strerror(errno)); goto out; } if (avc_using_threads) { avc_netlink_thread = avc_create_thread(&avc_netlink_loop); avc_netlink_trouble = 0; } avc_running = 1; out: return rc; } void avc_cache_stats(struct avc_cache_stats *p) { memcpy(p, &cache_stats, sizeof(cache_stats)); } void avc_sid_stats(void) { /* avc_init needs to be called before this function */ assert(avc_running); avc_get_lock(avc_log_lock); avc_get_lock(avc_lock); sidtab_sid_stats(&avc_sidtab, avc_audit_buf, AVC_AUDIT_BUFSIZE); avc_release_lock(avc_lock); avc_log(SELINUX_INFO, "%s", avc_audit_buf); avc_release_lock(avc_log_lock); } void avc_av_stats(void) { int i, chain_len, max_chain_len, slots_used; struct avc_node *node; avc_get_lock(avc_lock); slots_used = 0; max_chain_len = 0; for (i = 0; i < AVC_CACHE_SLOTS; i++) { node = avc_cache.slots[i]; if (node) { slots_used++; chain_len = 0; while (node) { chain_len++; node = node->next; } if (chain_len > max_chain_len) max_chain_len = chain_len; } } avc_release_lock(avc_lock); avc_log(SELINUX_INFO, "%s: %d AV entries and %d/%d buckets used, " "longest chain length %d\n", avc_prefix, avc_cache.active_nodes, slots_used, AVC_CACHE_SLOTS, max_chain_len); } hidden_def(avc_av_stats) static inline struct avc_node *avc_reclaim_node(void) { struct avc_node *prev, *cur; int try; uint32_t hvalue; hvalue = avc_cache.lru_hint; for (try = 0; try < 2; try++) { do { prev = NULL; cur = avc_cache.slots[hvalue]; while (cur) { if (!cur->ae.used) goto found; cur->ae.used = 0; prev = cur; cur = cur->next; } hvalue = (hvalue + 1) & (AVC_CACHE_SLOTS - 1); } while (hvalue != avc_cache.lru_hint); } errno = ENOMEM; /* this was a panic in the kernel... */ return NULL; found: avc_cache.lru_hint = hvalue; if (prev == NULL) avc_cache.slots[hvalue] = cur->next; else prev->next = cur->next; return cur; } static inline void avc_clear_avc_entry(struct avc_entry *ae) { memset(ae, 0, sizeof(*ae)); } static inline struct avc_node *avc_claim_node(security_id_t ssid, security_id_t tsid, security_class_t tclass) { struct avc_node *new; int hvalue; if (!avc_node_freelist) avc_cleanup(); if (avc_node_freelist) { new = avc_node_freelist; avc_node_freelist = avc_node_freelist->next; avc_cache.active_nodes++; } else { new = avc_reclaim_node(); if (!new) goto out; } hvalue = avc_hash(ssid, tsid, tclass); avc_clear_avc_entry(&new->ae); new->ae.used = 1; new->ae.ssid = ssid; new->ae.tsid = tsid; new->ae.tclass = tclass; new->next = avc_cache.slots[hvalue]; avc_cache.slots[hvalue] = new; out: return new; } static inline struct avc_node *avc_search_node(security_id_t ssid, security_id_t tsid, security_class_t tclass, int *probes) { struct avc_node *cur; int hvalue; int tprobes = 1; hvalue = avc_hash(ssid, tsid, tclass); cur = avc_cache.slots[hvalue]; while (cur != NULL && (ssid != cur->ae.ssid || tclass != cur->ae.tclass || tsid != cur->ae.tsid)) { tprobes++; cur = cur->next; } if (cur == NULL) { /* cache miss */ goto out; } /* cache hit */ if (probes) *probes = tprobes; cur->ae.used = 1; out: return cur; } /** * avc_lookup - Look up an AVC entry. * @ssid: source security identifier * @tsid: target security identifier * @tclass: target security class * @requested: requested permissions, interpreted based on @tclass * @aeref: AVC entry reference * * Look up an AVC entry that is valid for the * @requested permissions between the SID pair * (@ssid, @tsid), interpreting the permissions * based on @tclass. If a valid AVC entry exists, * then this function updates @aeref to refer to the * entry and returns %0. Otherwise, -1 is returned. */ static int avc_lookup(security_id_t ssid, security_id_t tsid, security_class_t tclass, access_vector_t requested, struct avc_entry_ref *aeref) { struct avc_node *node; int probes, rc = 0; avc_cache_stats_incr(cav_lookups); node = avc_search_node(ssid, tsid, tclass, &probes); if (node && ((node->ae.avd.decided & requested) == requested)) { avc_cache_stats_incr(cav_hits); avc_cache_stats_add(cav_probes, probes); aeref->ae = &node->ae; goto out; } avc_cache_stats_incr(cav_misses); rc = -1; out: return rc; } /** * avc_insert - Insert an AVC entry. * @ssid: source security identifier * @tsid: target security identifier * @tclass: target security class * @ae: AVC entry * @aeref: AVC entry reference * * Insert an AVC entry for the SID pair * (@ssid, @tsid) and class @tclass. * The access vectors and the sequence number are * normally provided by the security server in * response to a security_compute_av() call. If the * sequence number @ae->avd.seqno is not less than the latest * revocation notification, then the function copies * the access vectors into a cache entry, updates * @aeref to refer to the entry, and returns %0. * Otherwise, this function returns -%1 with @errno set to %EAGAIN. */ static int avc_insert(security_id_t ssid, security_id_t tsid, security_class_t tclass, struct avc_entry *ae, struct avc_entry_ref *aeref) { struct avc_node *node; int rc = 0; if (ae->avd.seqno < avc_cache.latest_notif) { avc_log(SELINUX_WARNING, "%s: seqno %d < latest_notif %d\n", avc_prefix, ae->avd.seqno, avc_cache.latest_notif); errno = EAGAIN; rc = -1; goto out; } node = avc_claim_node(ssid, tsid, tclass); if (!node) { rc = -1; goto out; } memcpy(&node->ae.avd, &ae->avd, sizeof(ae->avd)); aeref->ae = &node->ae; out: return rc; } void avc_cleanup(void) { } hidden_def(avc_cleanup) int avc_reset(void) { struct avc_callback_node *c; int i, ret, rc = 0, errsave = 0; struct avc_node *node, *tmp; errno = 0; if (!avc_running) return 0; avc_get_lock(avc_lock); for (i = 0; i < AVC_CACHE_SLOTS; i++) { node = avc_cache.slots[i]; while (node) { tmp = node; node = node->next; avc_clear_avc_entry(&tmp->ae); tmp->next = avc_node_freelist; avc_node_freelist = tmp; avc_cache.active_nodes--; } avc_cache.slots[i] = 0; } avc_cache.lru_hint = 0; avc_release_lock(avc_lock); memset(&cache_stats, 0, sizeof(cache_stats)); for (c = avc_callbacks; c; c = c->next) { if (c->events & AVC_CALLBACK_RESET) { ret = c->callback(AVC_CALLBACK_RESET, 0, 0, 0, 0, 0); if (ret && !rc) { rc = ret; errsave = errno; } } } errno = errsave; return rc; } hidden_def(avc_reset) void avc_destroy(void) { struct avc_callback_node *c; struct avc_node *node, *tmp; int i; /* avc_init needs to be called before this function */ assert(avc_running); avc_get_lock(avc_lock); if (avc_using_threads) avc_stop_thread(avc_netlink_thread); avc_netlink_close(); for (i = 0; i < AVC_CACHE_SLOTS; i++) { node = avc_cache.slots[i]; while (node) { tmp = node; node = node->next; avc_free(tmp); } } while (avc_node_freelist) { tmp = avc_node_freelist; avc_node_freelist = tmp->next; avc_free(tmp); } avc_release_lock(avc_lock); while (avc_callbacks) { c = avc_callbacks; avc_callbacks = c->next; avc_free(c); } sidtab_destroy(&avc_sidtab); avc_free_lock(avc_lock); avc_free_lock(avc_log_lock); avc_free(avc_audit_buf); avc_running = 0; } /* ratelimit stuff put aside for now --EFW */ #if 0 /* * Copied from net/core/utils.c:net_ratelimit and modified for * use by the AVC audit facility. */ #define AVC_MSG_COST 5*HZ #define AVC_MSG_BURST 10*5*HZ /* * This enforces a rate limit: not more than one kernel message * every 5secs to make a denial-of-service attack impossible. */ static int avc_ratelimit(void) { static unsigned long toks = 10 * 5 * HZ; static unsigned long last_msg; static int missed, rc = 0; unsigned long now = jiffies; void *ratelimit_lock = avc_alloc_lock(); avc_get_lock(ratelimit_lock); toks += now - last_msg; last_msg = now; if (toks > AVC_MSG_BURST) toks = AVC_MSG_BURST; if (toks >= AVC_MSG_COST) { int lost = missed; missed = 0; toks -= AVC_MSG_COST; avc_release_lock(ratelimit_lock); if (lost) { avc_log(SELINUX_WARNING, "%s: %d messages suppressed.\n", avc_prefix, lost); } rc = 1; goto out; } missed++; avc_release_lock(ratelimit_lock); out: avc_free_lock(ratelimit_lock); return rc; } static inline int check_avc_ratelimit(void) { if (avc_enforcing) return avc_ratelimit(); else { /* If permissive, then never suppress messages. */ return 1; } } #endif /* ratelimit stuff */ /** * avc_dump_av - Display an access vector in human-readable form. * @tclass: target security class * @av: access vector */ static void avc_dump_av(security_class_t tclass, access_vector_t av) { const char *permstr; access_vector_t bit = 1; if (av == 0) { log_append(avc_audit_buf, " null"); return; } log_append(avc_audit_buf, " {"); while (av) { if (av & bit) { permstr = security_av_perm_to_string(tclass, bit); if (!permstr) break; log_append(avc_audit_buf, " %s", permstr); av &= ~bit; } bit <<= 1; } if (av) log_append(avc_audit_buf, " 0x%x", av); log_append(avc_audit_buf, " }"); } /** * avc_dump_query - Display a SID pair and a class in human-readable form. * @ssid: source security identifier * @tsid: target security identifier * @tclass: target security class */ static void avc_dump_query(security_id_t ssid, security_id_t tsid, security_class_t tclass) { avc_get_lock(avc_lock); log_append(avc_audit_buf, "scontext=%s tcontext=%s", ssid->ctx, tsid->ctx); avc_release_lock(avc_lock); log_append(avc_audit_buf, " tclass=%s", security_class_to_string(tclass)); } void avc_audit(security_id_t ssid, security_id_t tsid, security_class_t tclass, access_vector_t requested, struct av_decision *avd, int result, void *a) { access_vector_t denied, audited; denied = requested & ~avd->allowed; if (denied) audited = denied & avd->auditdeny; else if (!requested || result) audited = denied = requested; else audited = requested & avd->auditallow; if (!audited) return; #if 0 if (!check_avc_ratelimit()) return; #endif /* prevent overlapping buffer writes */ avc_get_lock(avc_log_lock); snprintf(avc_audit_buf, AVC_AUDIT_BUFSIZE, "%s: %s ", avc_prefix, (denied || !requested) ? "denied" : "granted"); avc_dump_av(tclass, audited); log_append(avc_audit_buf, " for "); /* get any extra information printed by the callback */ avc_suppl_audit(a, tclass, avc_audit_buf + strlen(avc_audit_buf), AVC_AUDIT_BUFSIZE - strlen(avc_audit_buf)); log_append(avc_audit_buf, " "); avc_dump_query(ssid, tsid, tclass); /* append permissive=0|1 like the kernel at the end */ if (denied || !requested) log_append(avc_audit_buf, " permissive=%d", !result); log_append(avc_audit_buf, "\n"); avc_log(SELINUX_AVC, "%s", avc_audit_buf); avc_release_lock(avc_log_lock); } hidden_def(avc_audit) static void avd_init(struct av_decision *avd) { avd->allowed = 0; avd->auditallow = 0; avd->auditdeny = 0xffffffff; avd->seqno = avc_cache.latest_notif; avd->flags = 0; } int avc_has_perm_noaudit(security_id_t ssid, security_id_t tsid, security_class_t tclass, access_vector_t requested, struct avc_entry_ref *aeref, struct av_decision *avd) { struct avc_entry *ae; int rc = 0; struct avc_entry entry; access_vector_t denied; struct avc_entry_ref ref; if (avd) avd_init(avd); if (!avc_using_threads && !avc_app_main_loop) { (void)avc_netlink_check_nb(); } if (!aeref) { avc_entry_ref_init(&ref); aeref = &ref; } avc_get_lock(avc_lock); avc_cache_stats_incr(entry_lookups); ae = aeref->ae; if (ae) { if (ae->ssid == ssid && ae->tsid == tsid && ae->tclass == tclass && ((ae->avd.decided & requested) == requested)) { avc_cache_stats_incr(entry_hits); ae->used = 1; } else { avc_cache_stats_incr(entry_discards); ae = 0; } } if (!ae) { avc_cache_stats_incr(entry_misses); rc = avc_lookup(ssid, tsid, tclass, requested, aeref); if (rc) { rc = security_compute_av(ssid->ctx, tsid->ctx, tclass, requested, &entry.avd); if (rc && errno == EINVAL && !avc_enforcing) { rc = errno = 0; goto out; } if (rc) goto out; rc = avc_insert(ssid, tsid, tclass, &entry, aeref); if (rc) goto out; } ae = aeref->ae; } if (avd) memcpy(avd, &ae->avd, sizeof(*avd)); denied = requested & ~(ae->avd.allowed); if (!requested || denied) { if (!avc_enforcing || (ae->avd.flags & SELINUX_AVD_FLAGS_PERMISSIVE)) ae->avd.allowed |= requested; else { errno = EACCES; rc = -1; } } out: avc_release_lock(avc_lock); return rc; } hidden_def(avc_has_perm_noaudit) int avc_has_perm(security_id_t ssid, security_id_t tsid, security_class_t tclass, access_vector_t requested, struct avc_entry_ref *aeref, void *auditdata) { struct av_decision avd; int errsave, rc; rc = avc_has_perm_noaudit(ssid, tsid, tclass, requested, aeref, &avd); errsave = errno; avc_audit(ssid, tsid, tclass, requested, &avd, rc, auditdata); errno = errsave; return rc; } int avc_compute_create(security_id_t ssid, security_id_t tsid, security_class_t tclass, security_id_t *newsid) { int rc; struct avc_entry_ref aeref; struct avc_entry entry; char * ctx; *newsid = NULL; avc_entry_ref_init(&aeref); avc_get_lock(avc_lock); /* check for a cached entry */ rc = avc_lookup(ssid, tsid, tclass, 0, &aeref); if (rc) { /* need to make a cache entry for this tuple */ rc = security_compute_av(ssid->ctx, tsid->ctx, tclass, 0, &entry.avd); if (rc) goto out; rc = avc_insert(ssid, tsid, tclass, &entry, &aeref); if (rc) goto out; } /* check for a saved compute_create value */ if (!aeref.ae->create_sid) { /* need to query the kernel policy */ rc = security_compute_create(ssid->ctx, tsid->ctx, tclass, &ctx); if (rc) goto out; rc = sidtab_context_to_sid(&avc_sidtab, ctx, newsid); freecon(ctx); if (rc) goto out; aeref.ae->create_sid = *newsid; } else { /* found saved value */ *newsid = aeref.ae->create_sid; } rc = 0; out: avc_release_lock(avc_lock); return rc; } int avc_add_callback(int (*callback) (uint32_t event, security_id_t ssid, security_id_t tsid, security_class_t tclass, access_vector_t perms, access_vector_t * out_retained), uint32_t events, security_id_t ssid, security_id_t tsid, security_class_t tclass, access_vector_t perms) { struct avc_callback_node *c; int rc = 0; c = avc_malloc(sizeof(*c)); if (!c) { rc = -1; goto out; } c->callback = callback; c->events = events; c->ssid = ssid; c->tsid = tsid; c->tclass = tclass; c->perms = perms; c->next = avc_callbacks; avc_callbacks = c; out: return rc; } static inline int avc_sidcmp(security_id_t x, security_id_t y) { return (x == y || x == SECSID_WILD || y == SECSID_WILD); } static inline void avc_update_node(uint32_t event, struct avc_node *node, access_vector_t perms) { switch (event) { case AVC_CALLBACK_GRANT: node->ae.avd.allowed |= perms; break; case AVC_CALLBACK_TRY_REVOKE: case AVC_CALLBACK_REVOKE: node->ae.avd.allowed &= ~perms; break; case AVC_CALLBACK_AUDITALLOW_ENABLE: node->ae.avd.auditallow |= perms; break; case AVC_CALLBACK_AUDITALLOW_DISABLE: node->ae.avd.auditallow &= ~perms; break; case AVC_CALLBACK_AUDITDENY_ENABLE: node->ae.avd.auditdeny |= perms; break; case AVC_CALLBACK_AUDITDENY_DISABLE: node->ae.avd.auditdeny &= ~perms; break; } } static int avc_update_cache(uint32_t event, security_id_t ssid, security_id_t tsid, security_class_t tclass, access_vector_t perms) { struct avc_node *node; int i; avc_get_lock(avc_lock); if (ssid == SECSID_WILD || tsid == SECSID_WILD) { /* apply to all matching nodes */ for (i = 0; i < AVC_CACHE_SLOTS; i++) { for (node = avc_cache.slots[i]; node; node = node->next) { if (avc_sidcmp(ssid, node->ae.ssid) && avc_sidcmp(tsid, node->ae.tsid) && tclass == node->ae.tclass) { avc_update_node(event, node, perms); } } } } else { /* apply to one node */ node = avc_search_node(ssid, tsid, tclass, 0); if (node) { avc_update_node(event, node, perms); } } avc_release_lock(avc_lock); return 0; } /* avc_control - update cache and call callbacks * * This should not be called directly; use the individual event * functions instead. */ static int avc_control(uint32_t event, security_id_t ssid, security_id_t tsid, security_class_t tclass, access_vector_t perms, uint32_t seqno, access_vector_t * out_retained) { struct avc_callback_node *c; access_vector_t tretained = 0, cretained = 0; int ret, rc = 0, errsave = 0; errno = 0; /* * try_revoke only removes permissions from the cache * state if they are not retained by the object manager. * Hence, try_revoke must wait until after the callbacks have * been invoked to update the cache state. */ if (event != AVC_CALLBACK_TRY_REVOKE) avc_update_cache(event, ssid, tsid, tclass, perms); for (c = avc_callbacks; c; c = c->next) { if ((c->events & event) && avc_sidcmp(c->ssid, ssid) && avc_sidcmp(c->tsid, tsid) && c->tclass == tclass && (c->perms & perms)) { cretained = 0; ret = c->callback(event, ssid, tsid, tclass, (c->perms & perms), &cretained); if (ret && !rc) { rc = ret; errsave = errno; } if (!ret) tretained |= cretained; } } if (event == AVC_CALLBACK_TRY_REVOKE) { /* revoke any unretained permissions */ perms &= ~tretained; avc_update_cache(event, ssid, tsid, tclass, perms); *out_retained = tretained; } avc_get_lock(avc_lock); if (seqno > avc_cache.latest_notif) avc_cache.latest_notif = seqno; avc_release_lock(avc_lock); errno = errsave; return rc; } /** * avc_ss_grant - Grant previously denied permissions. * @ssid: source security identifier or %SECSID_WILD * @tsid: target security identifier or %SECSID_WILD * @tclass: target security class * @perms: permissions to grant * @seqno: policy sequence number */ int avc_ss_grant(security_id_t ssid, security_id_t tsid, security_class_t tclass, access_vector_t perms, uint32_t seqno) { return avc_control(AVC_CALLBACK_GRANT, ssid, tsid, tclass, perms, seqno, 0); } /** * avc_ss_try_revoke - Try to revoke previously granted permissions. * @ssid: source security identifier or %SECSID_WILD * @tsid: target security identifier or %SECSID_WILD * @tclass: target security class * @perms: permissions to grant * @seqno: policy sequence number * @out_retained: subset of @perms that are retained * * Try to revoke previously granted permissions, but * only if they are not retained as migrated permissions. * Return the subset of permissions that are retained via @out_retained. */ int avc_ss_try_revoke(security_id_t ssid, security_id_t tsid, security_class_t tclass, access_vector_t perms, uint32_t seqno, access_vector_t * out_retained) { return avc_control(AVC_CALLBACK_TRY_REVOKE, ssid, tsid, tclass, perms, seqno, out_retained); } /** * avc_ss_revoke - Revoke previously granted permissions. * @ssid: source security identifier or %SECSID_WILD * @tsid: target security identifier or %SECSID_WILD * @tclass: target security class * @perms: permissions to grant * @seqno: policy sequence number * * Revoke previously granted permissions, even if * they are retained as migrated permissions. */ int avc_ss_revoke(security_id_t ssid, security_id_t tsid, security_class_t tclass, access_vector_t perms, uint32_t seqno) { return avc_control(AVC_CALLBACK_REVOKE, ssid, tsid, tclass, perms, seqno, 0); } /** * avc_ss_reset - Flush the cache and revalidate migrated permissions. * @seqno: policy sequence number */ int avc_ss_reset(uint32_t seqno) { int rc; rc = avc_reset(); avc_get_lock(avc_lock); if (seqno > avc_cache.latest_notif) avc_cache.latest_notif = seqno; avc_release_lock(avc_lock); return rc; } /** * avc_ss_set_auditallow - Enable or disable auditing of granted permissions. * @ssid: source security identifier or %SECSID_WILD * @tsid: target security identifier or %SECSID_WILD * @tclass: target security class * @perms: permissions to grant * @seqno: policy sequence number * @enable: enable flag. */ int avc_ss_set_auditallow(security_id_t ssid, security_id_t tsid, security_class_t tclass, access_vector_t perms, uint32_t seqno, uint32_t enable) { if (enable) return avc_control(AVC_CALLBACK_AUDITALLOW_ENABLE, ssid, tsid, tclass, perms, seqno, 0); else return avc_control(AVC_CALLBACK_AUDITALLOW_DISABLE, ssid, tsid, tclass, perms, seqno, 0); } /** * avc_ss_set_auditdeny - Enable or disable auditing of denied permissions. * @ssid: source security identifier or %SECSID_WILD * @tsid: target security identifier or %SECSID_WILD * @tclass: target security class * @perms: permissions to grant * @seqno: policy sequence number * @enable: enable flag. */ int avc_ss_set_auditdeny(security_id_t ssid, security_id_t tsid, security_class_t tclass, access_vector_t perms, uint32_t seqno, uint32_t enable) { if (enable) return avc_control(AVC_CALLBACK_AUDITDENY_ENABLE, ssid, tsid, tclass, perms, seqno, 0); else return avc_control(AVC_CALLBACK_AUDITDENY_DISABLE, ssid, tsid, tclass, perms, seqno, 0); } src/avc_internal.c0100644 0000000 0000000 00000013570 12763246076 013230 0ustar000000000 0000000 /* * Callbacks for user-supplied memory allocation, supplemental * auditing, and locking routines. * * Author : Eamon Walsh * * Netlink code derived in part from sample code by * James Morris . */ #include #include #include #include #include #include #include #include #include #include #include #include #include "callbacks.h" #include "selinux_netlink.h" #include "avc_internal.h" #ifndef NETLINK_SELINUX #define NETLINK_SELINUX 7 #endif /* callback pointers */ void *(*avc_func_malloc) (size_t) = NULL; void (*avc_func_free) (void *) = NULL; void (*avc_func_log) (const char *, ...) = NULL; void (*avc_func_audit) (void *, security_class_t, char *, size_t) = NULL; int avc_using_threads = 0; int avc_app_main_loop = 0; void *(*avc_func_create_thread) (void (*)(void)) = NULL; void (*avc_func_stop_thread) (void *) = NULL; void *(*avc_func_alloc_lock) (void) = NULL; void (*avc_func_get_lock) (void *) = NULL; void (*avc_func_release_lock) (void *) = NULL; void (*avc_func_free_lock) (void *) = NULL; /* message prefix string and avc enforcing mode */ char avc_prefix[AVC_PREFIX_SIZE] = "uavc"; int avc_running = 0; int avc_enforcing = 1; int avc_setenforce = 0; int avc_netlink_trouble = 0; /* netlink socket code */ static int fd = -1; int avc_netlink_open(int blocking) { int len, rc = 0; struct sockaddr_nl addr; fd = socket(PF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_SELINUX); if (fd < 0) { rc = fd; goto out; } if (!blocking && fcntl(fd, F_SETFL, O_NONBLOCK)) { close(fd); fd = -1; rc = -1; goto out; } len = sizeof(addr); memset(&addr, 0, len); addr.nl_family = AF_NETLINK; addr.nl_groups = SELNL_GRP_AVC; if (bind(fd, (struct sockaddr *)&addr, len) < 0) { close(fd); fd = -1; rc = -1; goto out; } out: return rc; } void avc_netlink_close(void) { if (fd >= 0) close(fd); fd = -1; } static int avc_netlink_receive(char *buf, unsigned buflen, int blocking) { int rc; struct pollfd pfd = { fd, POLLIN | POLLPRI, 0 }; struct sockaddr_nl nladdr; socklen_t nladdrlen = sizeof nladdr; struct nlmsghdr *nlh = (struct nlmsghdr *)buf; do { rc = poll(&pfd, 1, (blocking ? -1 : 0)); } while (rc < 0 && errno == EINTR); if (rc == 0 && !blocking) { errno = EWOULDBLOCK; return -1; } else if (rc < 1) { avc_log(SELINUX_ERROR, "%s: netlink poll: error %d\n", avc_prefix, errno); return rc; } rc = recvfrom(fd, buf, buflen, 0, (struct sockaddr *)&nladdr, &nladdrlen); if (rc < 0) return rc; if (nladdrlen != sizeof nladdr) { avc_log(SELINUX_WARNING, "%s: warning: netlink address truncated, len %d?\n", avc_prefix, nladdrlen); return -1; } if (nladdr.nl_pid) { avc_log(SELINUX_WARNING, "%s: warning: received spoofed netlink packet from: %d\n", avc_prefix, nladdr.nl_pid); return -1; } if (rc == 0) { avc_log(SELINUX_WARNING, "%s: warning: received EOF on netlink socket\n", avc_prefix); errno = EBADFD; return -1; } if (nlh->nlmsg_flags & MSG_TRUNC || nlh->nlmsg_len > (unsigned)rc) { avc_log(SELINUX_WARNING, "%s: warning: incomplete netlink message\n", avc_prefix); return -1; } return 0; } static int avc_netlink_process(char *buf) { int rc; struct nlmsghdr *nlh = (struct nlmsghdr *)buf; switch (nlh->nlmsg_type) { case NLMSG_ERROR:{ struct nlmsgerr *err = NLMSG_DATA(nlh); /* Netlink ack */ if (err->error == 0) break; errno = -err->error; avc_log(SELINUX_ERROR, "%s: netlink error: %d\n", avc_prefix, errno); return -1; } case SELNL_MSG_SETENFORCE:{ struct selnl_msg_setenforce *msg = NLMSG_DATA(nlh); avc_log(SELINUX_INFO, "%s: received setenforce notice (enforcing=%d)\n", avc_prefix, msg->val); if (avc_setenforce) break; avc_enforcing = msg->val; if (avc_enforcing && (rc = avc_ss_reset(0)) < 0) { avc_log(SELINUX_ERROR, "%s: cache reset returned %d (errno %d)\n", avc_prefix, rc, errno); return rc; } rc = selinux_netlink_setenforce(msg->val); if (rc < 0) return rc; break; } case SELNL_MSG_POLICYLOAD:{ struct selnl_msg_policyload *msg = NLMSG_DATA(nlh); avc_log(SELINUX_INFO, "%s: received policyload notice (seqno=%d)\n", avc_prefix, msg->seqno); rc = avc_ss_reset(msg->seqno); if (rc < 0) { avc_log(SELINUX_ERROR, "%s: cache reset returned %d (errno %d)\n", avc_prefix, rc, errno); return rc; } rc = selinux_netlink_policyload(msg->seqno); if (rc < 0) return rc; break; } default: avc_log(SELINUX_WARNING, "%s: warning: unknown netlink message %d\n", avc_prefix, nlh->nlmsg_type); } return 0; } int avc_netlink_check_nb(void) { int rc; char buf[1024] __attribute__ ((aligned)); while (1) { errno = 0; rc = avc_netlink_receive(buf, sizeof(buf), 0); if (rc < 0) { if (errno == EWOULDBLOCK) return 0; if (errno == 0 || errno == EINTR) continue; else { avc_log(SELINUX_ERROR, "%s: netlink recvfrom: error %d\n", avc_prefix, errno); return rc; } } (void)avc_netlink_process(buf); } return 0; } /* run routine for the netlink listening thread */ void avc_netlink_loop(void) { int rc; char buf[1024] __attribute__ ((aligned)); while (1) { errno = 0; rc = avc_netlink_receive(buf, sizeof(buf), 1); if (rc < 0) { if (errno == 0 || errno == EINTR) continue; else { avc_log(SELINUX_ERROR, "%s: netlink recvfrom: error %d\n", avc_prefix, errno); break; } } rc = avc_netlink_process(buf); if (rc < 0) break; } close(fd); fd = -1; avc_netlink_trouble = 1; avc_log(SELINUX_ERROR, "%s: netlink thread: errors encountered, terminating\n", avc_prefix); } int avc_netlink_acquire_fd(void) { avc_app_main_loop = 1; return fd; } void avc_netlink_release_fd(void) { avc_app_main_loop = 0; } src/avc_internal.h0100644 0000000 0000000 00000011702 12763246076 013230 0ustar000000000 0000000 /* * This file describes the internal interface used by the AVC * for calling the user-supplied memory allocation, supplemental * auditing, and locking routine, as well as incrementing the * statistics fields. * * Author : Eamon Walsh */ #ifndef _SELINUX_AVC_INTERNAL_H_ #define _SELINUX_AVC_INTERNAL_H_ #include #include #include #include #include "callbacks.h" #include "dso.h" /* callback pointers */ extern void *(*avc_func_malloc) (size_t) hidden; extern void (*avc_func_free) (void *)hidden; extern void (*avc_func_log) (const char *, ...)hidden; extern void (*avc_func_audit) (void *, security_class_t, char *, size_t)hidden; extern int avc_using_threads hidden; extern int avc_app_main_loop hidden; extern void *(*avc_func_create_thread) (void (*)(void))hidden; extern void (*avc_func_stop_thread) (void *)hidden; extern void *(*avc_func_alloc_lock) (void)hidden; extern void (*avc_func_get_lock) (void *)hidden; extern void (*avc_func_release_lock) (void *)hidden; extern void (*avc_func_free_lock) (void *)hidden; static inline void set_callbacks(const struct avc_memory_callback *mem_cb, const struct avc_log_callback *log_cb, const struct avc_thread_callback *thread_cb, const struct avc_lock_callback *lock_cb) { if (mem_cb) { avc_func_malloc = mem_cb->func_malloc; avc_func_free = mem_cb->func_free; } if (log_cb) { avc_func_log = log_cb->func_log; avc_func_audit = log_cb->func_audit; } if (thread_cb) { avc_using_threads = 1; avc_func_create_thread = thread_cb->func_create_thread; avc_func_stop_thread = thread_cb->func_stop_thread; } if (lock_cb) { avc_func_alloc_lock = lock_cb->func_alloc_lock; avc_func_get_lock = lock_cb->func_get_lock; avc_func_release_lock = lock_cb->func_release_lock; avc_func_free_lock = lock_cb->func_free_lock; } } /* message prefix and enforcing mode*/ #define AVC_PREFIX_SIZE 16 extern char avc_prefix[AVC_PREFIX_SIZE] hidden; extern int avc_running hidden; extern int avc_enforcing hidden; extern int avc_setenforce hidden; /* user-supplied callback interface for avc */ static inline void *avc_malloc(size_t size) { return avc_func_malloc ? avc_func_malloc(size) : malloc(size); } static inline void avc_free(void *ptr) { if (avc_func_free) avc_func_free(ptr); else free(ptr); } /* this is a macro in order to use the variadic capability. */ #define avc_log(type, format...) \ if (avc_func_log) \ avc_func_log(format); \ else \ selinux_log(type, format); static inline void avc_suppl_audit(void *ptr, security_class_t class, char *buf, size_t len) { if (avc_func_audit) avc_func_audit(ptr, class, buf, len); else selinux_audit(ptr, class, buf, len); } static inline void *avc_create_thread(void (*run) (void)) { return avc_func_create_thread ? avc_func_create_thread(run) : NULL; } static inline void avc_stop_thread(void *thread) { if (avc_func_stop_thread) avc_func_stop_thread(thread); } static inline void *avc_alloc_lock(void) { return avc_func_alloc_lock ? avc_func_alloc_lock() : NULL; } static inline void avc_get_lock(void *lock) { if (avc_func_get_lock) avc_func_get_lock(lock); } static inline void avc_release_lock(void *lock) { if (avc_func_release_lock) avc_func_release_lock(lock); } static inline void avc_free_lock(void *lock) { if (avc_func_free_lock) avc_func_free_lock(lock); } /* statistics helper routines */ #ifdef AVC_CACHE_STATS #define avc_cache_stats_incr(field) \ cache_stats.field ++; #define avc_cache_stats_add(field, num) \ cache_stats.field += num; #else #define avc_cache_stats_incr(field) #define avc_cache_stats_add(field, num) #endif /* logging helper routines */ #define AVC_AUDIT_BUFSIZE 1024 /* again, we need the variadic capability here */ #define log_append(buf,format...) \ snprintf(buf+strlen(buf), AVC_AUDIT_BUFSIZE-strlen(buf), format) /* internal callbacks */ int avc_ss_grant(security_id_t ssid, security_id_t tsid, security_class_t tclass, access_vector_t perms, uint32_t seqno) hidden; int avc_ss_try_revoke(security_id_t ssid, security_id_t tsid, security_class_t tclass, access_vector_t perms, uint32_t seqno, access_vector_t * out_retained) hidden; int avc_ss_revoke(security_id_t ssid, security_id_t tsid, security_class_t tclass, access_vector_t perms, uint32_t seqno) hidden; int avc_ss_reset(uint32_t seqno) hidden; int avc_ss_set_auditallow(security_id_t ssid, security_id_t tsid, security_class_t tclass, access_vector_t perms, uint32_t seqno, uint32_t enable) hidden; int avc_ss_set_auditdeny(security_id_t ssid, security_id_t tsid, security_class_t tclass, access_vector_t perms, uint32_t seqno, uint32_t enable) hidden; /* netlink kernel message code */ extern int avc_netlink_trouble hidden; hidden_proto(avc_av_stats) hidden_proto(avc_cleanup) hidden_proto(avc_reset) hidden_proto(avc_audit) hidden_proto(avc_has_perm_noaudit) #endif /* _SELINUX_AVC_INTERNAL_H_ */ src/avc_sidtab.c0100644 0000000 0000000 00000005516 12763246076 012663 0ustar000000000 0000000 /* * Implementation of the userspace SID hashtable. * * Author : Eamon Walsh, */ #include #include #include #include #include #include "selinux_internal.h" #include #include "avc_sidtab.h" #include "avc_internal.h" static inline unsigned sidtab_hash(const char * key) { char *p, *keyp; unsigned int size; unsigned int val; val = 0; keyp = (char *)key; size = strlen(keyp); for (p = keyp; (unsigned int)(p - keyp) < size; p++) val = (val << 4 | (val >> (8 * sizeof(unsigned int) - 4))) ^ (*p); return val & (SIDTAB_SIZE - 1); } int sidtab_init(struct sidtab *s) { int i, rc = 0; s->htable = (struct sidtab_node **)avc_malloc (sizeof(struct sidtab_node *) * SIDTAB_SIZE); if (!s->htable) { rc = -1; goto out; } for (i = 0; i < SIDTAB_SIZE; i++) s->htable[i] = NULL; s->nel = 0; out: return rc; } int sidtab_insert(struct sidtab *s, const char * ctx) { int hvalue, rc = 0; struct sidtab_node *newnode; char * newctx; newnode = (struct sidtab_node *)avc_malloc(sizeof(*newnode)); if (!newnode) { rc = -1; goto out; } newctx = (char *) strdup(ctx); if (!newctx) { rc = -1; avc_free(newnode); goto out; } hvalue = sidtab_hash(newctx); newnode->next = s->htable[hvalue]; newnode->sid_s.ctx = newctx; newnode->sid_s.refcnt = 1; /* unused */ s->htable[hvalue] = newnode; s->nel++; out: return rc; } int sidtab_context_to_sid(struct sidtab *s, const char * ctx, security_id_t * sid) { int hvalue, rc = 0; struct sidtab_node *cur; *sid = NULL; hvalue = sidtab_hash(ctx); loop: cur = s->htable[hvalue]; while (cur != NULL && strcmp(cur->sid_s.ctx, ctx)) cur = cur->next; if (cur == NULL) { /* need to make a new entry */ rc = sidtab_insert(s, ctx); if (rc) goto out; goto loop; /* find the newly inserted node */ } *sid = &cur->sid_s; out: return rc; } void sidtab_sid_stats(struct sidtab *h, char *buf, int buflen) { int i, chain_len, slots_used, max_chain_len; struct sidtab_node *cur; slots_used = 0; max_chain_len = 0; for (i = 0; i < SIDTAB_SIZE; i++) { cur = h->htable[i]; if (cur) { slots_used++; chain_len = 0; while (cur) { chain_len++; cur = cur->next; } if (chain_len > max_chain_len) max_chain_len = chain_len; } } snprintf(buf, buflen, "%s: %d SID entries and %d/%d buckets used, longest " "chain length %d\n", avc_prefix, h->nel, slots_used, SIDTAB_SIZE, max_chain_len); } void sidtab_destroy(struct sidtab *s) { int i; struct sidtab_node *cur, *temp; if (!s) return; for (i = 0; i < SIDTAB_SIZE; i++) { cur = s->htable[i]; while (cur != NULL) { temp = cur; cur = cur->next; freecon(temp->sid_s.ctx); avc_free(temp); } s->htable[i] = NULL; } avc_free(s->htable); s->htable = NULL; } src/avc_sidtab.h0100644 0000000 0000000 00000001636 12763246076 012667 0ustar000000000 0000000 /* * A security identifier table (sidtab) is a hash table * of security context structures indexed by SID value. */ #ifndef _SELINUX_AVC_SIDTAB_H_ #define _SELINUX_AVC_SIDTAB_H_ #include #include #include "dso.h" struct sidtab_node { struct security_id sid_s; struct sidtab_node *next; }; #define SIDTAB_HASH_BITS 7 #define SIDTAB_HASH_BUCKETS (1 << SIDTAB_HASH_BITS) #define SIDTAB_HASH_MASK (SIDTAB_HASH_BUCKETS-1) #define SIDTAB_SIZE SIDTAB_HASH_BUCKETS struct sidtab { struct sidtab_node **htable; unsigned nel; }; int sidtab_init(struct sidtab *s) hidden; int sidtab_insert(struct sidtab *s, const char * ctx) hidden; int sidtab_context_to_sid(struct sidtab *s, const char * ctx, security_id_t * sid) hidden; void sidtab_sid_stats(struct sidtab *s, char *buf, int buflen) hidden; void sidtab_destroy(struct sidtab *s) hidden; #endif /* _SELINUX_AVC_SIDTAB_H_ */ src/booleans.c0100644 0000000 0000000 00000010503 12763246076 012356 0ustar000000000 0000000 /* * Author: Karl MacMillan * * Modified: * Dan Walsh - Added security_load_booleans(). */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "selinux_internal.h" #include "policy.h" #define SELINUX_BOOL_DIR "/booleans/" static int filename_select(const struct dirent *d) { if (d->d_name[0] == '.' && (d->d_name[1] == '\0' || (d->d_name[1] == '.' && d->d_name[2] == '\0'))) return 0; return 1; } int security_get_boolean_names(char ***names, int *len) { char path[PATH_MAX]; int i, rc; struct dirent **namelist; char **n; assert(len); if (!selinux_mnt) { errno = ENOENT; return -1; } snprintf(path, sizeof path, "%s%s", selinux_mnt, SELINUX_BOOL_DIR); *len = scandir(path, &namelist, &filename_select, alphasort); if (*len <= 0) { return -1; } n = (char **)malloc(sizeof(char *) * *len); if (!n) { rc = -1; goto bad; } for (i = 0; i < *len; i++) { n[i] = strdup(namelist[i]->d_name); if (!n[i]) { rc = -1; goto bad_freen; } } rc = 0; *names = n; out: for (i = 0; i < *len; i++) { free(namelist[i]); } free(namelist); return rc; bad_freen: for (--i; i >= 0; --i) free(n[i]); free(n); bad: goto out; } hidden_def(security_get_boolean_names) #define STRBUF_SIZE 3 static int get_bool_value(const char *name, char **buf) { int fd, len; char *fname = NULL; if (!selinux_mnt) { errno = ENOENT; return -1; } *buf = (char *)malloc(sizeof(char) * (STRBUF_SIZE + 1)); if (!*buf) goto out; (*buf)[STRBUF_SIZE] = 0; len = strlen(name) + strlen(selinux_mnt) + sizeof(SELINUX_BOOL_DIR); fname = (char *)malloc(sizeof(char) * len); if (!fname) goto out; snprintf(fname, len, "%s%s%s", selinux_mnt, SELINUX_BOOL_DIR, name); fd = open(fname, O_RDONLY); if (fd < 0) goto out; len = read(fd, *buf, STRBUF_SIZE); close(fd); if (len != STRBUF_SIZE) goto out; free(fname); return 0; out: if (*buf) free(*buf); if (fname) free(fname); return -1; } int security_get_boolean_pending(const char *name) { char *buf; int val; if (get_bool_value(name, &buf)) return -1; if (atoi(&buf[1])) val = 1; else val = 0; free(buf); return val; } int security_get_boolean_active(const char *name) { char *buf; int val; if (get_bool_value(name, &buf)) return -1; buf[1] = '\0'; if (atoi(buf)) val = 1; else val = 0; free(buf); return val; } hidden_def(security_get_boolean_active) int security_set_boolean(const char *name, int value) { int fd, ret, len; char buf[2], *fname; if (!selinux_mnt) { errno = ENOENT; return -1; } if (value < 0 || value > 1) { errno = EINVAL; return -1; } len = strlen(name) + strlen(selinux_mnt) + sizeof(SELINUX_BOOL_DIR); fname = (char *)malloc(sizeof(char) * len); if (!fname) return -1; snprintf(fname, len, "%s%s%s", selinux_mnt, SELINUX_BOOL_DIR, name); fd = open(fname, O_WRONLY); if (fd < 0) { ret = -1; goto out; } if (value) buf[0] = '1'; else buf[0] = '0'; buf[1] = '\0'; ret = write(fd, buf, 2); close(fd); out: free(fname); if (ret > 0) return 0; else return -1; } hidden_def(security_set_boolean) int security_commit_booleans(void) { int fd, ret; char buf[2]; char path[PATH_MAX]; if (!selinux_mnt) { errno = ENOENT; return -1; } snprintf(path, sizeof path, "%s/commit_pending_bools", selinux_mnt); fd = open(path, O_WRONLY); if (fd < 0) return -1; buf[0] = '1'; buf[1] = '\0'; ret = write(fd, buf, 2); close(fd); if (ret > 0) return 0; else return -1; } hidden_def(security_commit_booleans) static void rollback(SELboolean * boollist, int end) { int i; for (i = 0; i < end; i++) security_set_boolean(boollist[i].name, security_get_boolean_active(boollist[i]. name)); } int security_set_boolean_list(size_t boolcnt, SELboolean * const boollist, int permanent __attribute__((unused))) { size_t i; for (i = 0; i < boolcnt; i++) { if (security_set_boolean(boollist[i].name, boollist[i].value)) { rollback(boollist, i); return -1; } } /* OK, let's do the commit */ if (security_commit_booleans()) { return -1; } return 0; } src/callbacks.c0100644 0000000 0000000 00000004744 12763246076 012505 0ustar000000000 0000000 /* * User-supplied callbacks and default implementations. * Class and permission mappings. */ #include #include #include #include #include #include "callbacks.h" /* default implementations */ static int __attribute__ ((format(printf, 2, 3))) default_selinux_log(int type __attribute__((unused)), const char *fmt, ...) { int rc; va_list ap; va_start(ap, fmt); rc = vfprintf(stderr, fmt, ap); va_end(ap); return rc; } static int default_selinux_audit(void *ptr __attribute__((unused)), security_class_t cls __attribute__((unused)), char *buf __attribute__((unused)), size_t len __attribute__((unused))) { return 0; } static int default_selinux_validate(char **ctx) { return security_check_context(*ctx); } static int default_selinux_setenforce(int enforcing __attribute__((unused))) { return 0; } static int default_selinux_policyload(int seqno __attribute__((unused))) { return 0; } /* callback pointers */ int __attribute__ ((format(printf, 2, 3))) (*selinux_log)(int, const char *, ...) = default_selinux_log; int (*selinux_audit) (void *, security_class_t, char *, size_t) = default_selinux_audit; int (*selinux_validate)(char **ctx) = default_selinux_validate; int (*selinux_netlink_setenforce) (int enforcing) = default_selinux_setenforce; int (*selinux_netlink_policyload) (int seqno) = default_selinux_policyload; /* callback setting function */ void selinux_set_callback(int type, union selinux_callback cb) { switch (type) { case SELINUX_CB_LOG: selinux_log = cb.func_log; break; case SELINUX_CB_AUDIT: selinux_audit = cb.func_audit; break; case SELINUX_CB_VALIDATE: selinux_validate = cb.func_validate; break; case SELINUX_CB_SETENFORCE: selinux_netlink_setenforce = cb.func_setenforce; break; case SELINUX_CB_POLICYLOAD: selinux_netlink_policyload = cb.func_policyload; break; } } /* callback getting function */ union selinux_callback selinux_get_callback(int type) { union selinux_callback cb; switch (type) { case SELINUX_CB_LOG: cb.func_log = selinux_log; break; case SELINUX_CB_AUDIT: cb.func_audit = selinux_audit; break; case SELINUX_CB_VALIDATE: cb.func_validate = selinux_validate; break; case SELINUX_CB_SETENFORCE: cb.func_setenforce = selinux_netlink_setenforce; break; case SELINUX_CB_POLICYLOAD: cb.func_policyload = selinux_netlink_policyload; break; default: memset(&cb, 0, sizeof(cb)); errno = EINVAL; break; } return cb; } src/callbacks.h0100644 0000000 0000000 00000001356 12763246076 012506 0ustar000000000 0000000 /* * This file describes the callbacks passed to selinux_init() and available * for use from the library code. They all have default implementations. */ #ifndef _SELINUX_CALLBACKS_H_ #define _SELINUX_CALLBACKS_H_ #include #include #include #include #include "dso.h" /* callback pointers */ extern int __attribute__ ((format(printf, 2, 3))) (*selinux_log) (int type, const char *, ...) hidden; extern int (*selinux_audit) (void *, security_class_t, char *, size_t) hidden; extern int (*selinux_validate)(char **ctx) hidden; extern int (*selinux_netlink_setenforce) (int enforcing) hidden; extern int (*selinux_netlink_policyload) (int seqno) hidden; #endif /* _SELINUX_CALLBACKS_H_ */ src/canonicalize_context.c0100644 0000000 0000000 00000002065 12763246076 014763 0ustar000000000 0000000 #include #include #include #include #include #include #include #include "selinux_internal.h" #include "policy.h" #include int security_canonicalize_context(const char * con, char ** canoncon) { char path[PATH_MAX]; char *buf; size_t size; int fd, ret; if (!selinux_mnt) { errno = ENOENT; return -1; } snprintf(path, sizeof path, "%s/context", selinux_mnt); fd = open(path, O_RDWR); if (fd < 0) return -1; size = selinux_page_size; buf = malloc(size); if (!buf) { ret = -1; goto out; } strncpy(buf, con, size); ret = write(fd, buf, strlen(buf) + 1); if (ret < 0) goto out2; memset(buf, 0, size); ret = read(fd, buf, size - 1); if (ret < 0 && errno == EINVAL) { /* Fall back to the original context for kernels that do not support the extended interface. */ strncpy(buf, con, size); } *canoncon = strdup(buf); if (!(*canoncon)) { ret = -1; goto out2; } ret = 0; out2: free(buf); out: close(fd); return ret; } src/checkAccess.c0100644 0000000 0000000 00000002733 12763246076 012761 0ustar000000000 0000000 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ #include #include #include #include #include "selinux_internal.h" #include #include "avc_internal.h" static pthread_once_t once = PTHREAD_ONCE_INIT; static int selinux_enabled; static void avc_init_once(void) { selinux_enabled = is_selinux_enabled(); if (selinux_enabled == 1) avc_open(NULL, 0); } int selinux_check_access(const char * scon, const char * tcon, const char *class, const char *perm, void *aux) { int rc; security_id_t scon_id; security_id_t tcon_id; security_class_t sclass; access_vector_t av; __selinux_once(once, avc_init_once); if (selinux_enabled != 1) return 0; rc = avc_context_to_sid(scon, &scon_id); if (rc < 0) return rc; rc = avc_context_to_sid(tcon, &tcon_id); if (rc < 0) return rc; sclass = string_to_security_class(class); if (sclass == 0) { rc = errno; avc_log(SELINUX_ERROR, "Unknown class %s", class); if (security_deny_unknown() == 0) return 0; errno = rc; return -1; } av = string_to_av_perm(sclass, perm); if (av == 0) { rc = errno; avc_log(SELINUX_ERROR, "Unknown permission %s for class %s", perm, class); if (security_deny_unknown() == 0) return 0; errno = rc; return -1; } return avc_has_perm (scon_id, tcon_id, sclass, av, NULL, aux); } src/check_context.c0100644 0000000 0000000 00000001052 12763246076 013374 0ustar000000000 0000000 #include #include #include #include #include #include #include #include "selinux_internal.h" #include "policy.h" #include int security_check_context(const char * con) { char path[PATH_MAX]; int fd, ret; if (!selinux_mnt) { errno = ENOENT; return -1; } snprintf(path, sizeof path, "%s/context", selinux_mnt); fd = open(path, O_RDWR); if (fd < 0) return -1; ret = write(fd, con, strlen(con) + 1); close(fd); if (ret < 0) return -1; return 0; } src/compute_av.c0100644 0000000 0000000 00000002405 12763246076 012720 0ustar000000000 0000000 #include #include #include #include #include #include #include #include #include "selinux_internal.h" #include "policy.h" #include "mapping.h" int security_compute_av(const char * scon, const char * tcon, security_class_t tclass, access_vector_t requested, struct av_decision *avd) { char path[PATH_MAX]; char *buf; size_t len; int fd, ret; if (!selinux_mnt) { errno = ENOENT; return -1; } snprintf(path, sizeof path, "%s/access", selinux_mnt); fd = open(path, O_RDWR); if (fd < 0) return -1; len = selinux_page_size; buf = malloc(len); if (!buf) { ret = -1; goto out; } snprintf(buf, len, "%s %s %hu %x", scon, tcon, unmap_class(tclass), unmap_perm(tclass, requested)); ret = write(fd, buf, strlen(buf)); if (ret < 0) goto out2; memset(buf, 0, len); ret = read(fd, buf, len - 1); if (ret < 0) goto out2; ret = sscanf(buf, "%x %x %x %x %u %x", &avd->allowed, &avd->decided, &avd->auditallow, &avd->auditdeny, &avd->seqno, &avd->flags); if (ret < 5) { ret = -1; goto out2; } else if (ret < 6) avd->flags = 0; map_decision(tclass, avd); ret = 0; out2: free(buf); out: close(fd); return ret; } src/compute_create.c0100644 0000000 0000000 00000002004 12763246076 013550 0ustar000000000 0000000 #include #include #include #include #include #include #include #include #include "selinux_internal.h" #include "policy.h" #include "mapping.h" int security_compute_create(const char * scon, const char * tcon, security_class_t tclass, char ** newcon) { char path[PATH_MAX]; char *buf; size_t size; int fd, ret; if (!selinux_mnt) { errno = ENOENT; return -1; } snprintf(path, sizeof path, "%s/create", selinux_mnt); fd = open(path, O_RDWR); if (fd < 0) return -1; size = selinux_page_size; buf = malloc(size); if (!buf) { ret = -1; goto out; } snprintf(buf, size, "%s %s %hu", scon, tcon, unmap_class(tclass)); ret = write(fd, buf, strlen(buf)); if (ret < 0) goto out2; memset(buf, 0, size); ret = read(fd, buf, size - 1); if (ret < 0) goto out2; *newcon = strdup(buf); if (!(*newcon)) { ret = -1; goto out2; } ret = 0; out2: free(buf); out: close(fd); return ret; } src/context.c0100644 0000000 0000000 00000010017 12763246076 012240 0ustar000000000 0000000 #include "context_internal.h" #include #include #include #include #define COMP_USER 0 #define COMP_ROLE 1 #define COMP_TYPE 2 #define COMP_RANGE 3 typedef struct { char *current_str; /* This is made up-to-date only when needed */ char *(component[4]); } context_private_t; /* * Allocate a new context, initialized from str. There must be 3 or * 4 colon-separated components and no whitespace in any component other * than the MLS component. */ context_t context_new(const char *str) { int i, count; errno = 0; context_private_t *n = (context_private_t *) malloc(sizeof(context_private_t)); context_t result = (context_t) malloc(sizeof(context_s_t)); const char *p, *tok; if (result) result->ptr = n; else free(n); if (n == 0 || result == 0) { goto err; } n->current_str = n->component[0] = n->component[1] = n->component[2] = n->component[3] = 0; for (i = count = 0, p = str; *p; p++) { switch (*p) { case ':': count++; break; case '\n': case '\t': case '\r': goto err; /* sanity check */ case ' ': if (count < 3) goto err; /* sanity check */ } } /* * Could be anywhere from 2 - 5 * e.g user:role:type to user:role:type:sens1:cata-sens2:catb */ if (count < 2 || count > 5) { /* might not have a range */ goto err; } n->component[3] = 0; for (i = 0, tok = str; *tok; i++) { if (i < 3) for (p = tok; *p && *p != ':'; p++) { /* empty */ } else { /* MLS range is one component */ for (p = tok; *p; p++) { /* empty */ } } n->component[i] = (char *)malloc(p - tok + 1); if (n->component[i] == 0) goto err; strncpy(n->component[i], tok, p - tok); n->component[i][p - tok] = '\0'; tok = *p ? p + 1 : p; } return result; err: if (errno == 0) errno = EINVAL; context_free(result); return 0; } hidden_def(context_new) static void conditional_free(char **v) { if (*v) { free(*v); } *v = 0; } /* * free all storage used by a context. Safe to call with * null pointer. */ void context_free(context_t context) { context_private_t *n; int i; if (context) { n = context->ptr; if (n) { conditional_free(&n->current_str); for (i = 0; i < 4; i++) { conditional_free(&n->component[i]); } free(n); } free(context); } } hidden_def(context_free) /* * Return a pointer to the string value of the context. */ char *context_str(context_t context) { context_private_t *n = context->ptr; int i; size_t total = 0; conditional_free(&n->current_str); for (i = 0; i < 4; i++) { if (n->component[i]) { total += strlen(n->component[i]) + 1; } } n->current_str = malloc(total); if (n->current_str != 0) { char *cp = n->current_str; strcpy(cp, n->component[0]); cp += strlen(cp); for (i = 1; i < 4; i++) { if (n->component[i]) { *cp++ = ':'; strcpy(cp, n->component[i]); cp += strlen(cp); } } } return n->current_str; } hidden_def(context_str) /* Returns nonzero iff failed */ static int set_comp(context_private_t * n, int idx, const char *str) { char *t = NULL; const char *p; if (str) { t = (char *)malloc(strlen(str) + 1); if (!t) { return 1; } for (p = str; *p; p++) { if (*p == '\t' || *p == '\n' || *p == '\r' || ((*p == ':' || *p == ' ') && idx != COMP_RANGE)) { free(t); errno = EINVAL; return 1; } } strcpy(t, str); } conditional_free(&n->component[idx]); n->component[idx] = t; return 0; } #define def_get(name,tag) \ const char * context_ ## name ## _get(context_t context) \ { \ context_private_t *n = context->ptr; \ return n->component[tag]; \ } \ hidden_def(context_ ## name ## _get) def_get(type, COMP_TYPE) def_get(user, COMP_USER) def_get(range, COMP_RANGE) def_get(role, COMP_ROLE) #define def_set(name,tag) \ int context_ ## name ## _set(context_t context, const char* str) \ { \ return set_comp(context->ptr,tag,str);\ } \ hidden_def(context_ ## name ## _set) def_set(type, COMP_TYPE) def_set(role, COMP_ROLE) def_set(user, COMP_USER) def_set(range, COMP_RANGE) src/context_internal.h0100644 0000000 0000000 00000000640 12763246076 014142 0ustar000000000 0000000 #include #include "dso.h" hidden_proto(context_new) hidden_proto(context_free) hidden_proto(context_str) hidden_proto(context_type_set) hidden_proto(context_type_get) hidden_proto(context_role_set) hidden_proto(context_role_get) hidden_proto(context_user_set) hidden_proto(context_user_get) hidden_proto(context_range_set) hidden_proto(context_range_get) src/deny_unknown.c0100644 0000000 0000000 00000001314 12763246076 013272 0ustar000000000 0000000 #include #include #include #include #include #include #include "selinux_internal.h" #include "policy.h" #include #include int security_deny_unknown(void) { int fd, ret, deny_unknown = 0; char path[PATH_MAX]; char buf[20]; if (!selinux_mnt) { errno = ENOENT; return -1; } snprintf(path, sizeof(path), "%s/deny_unknown", selinux_mnt); fd = open(path, O_RDONLY); if (fd < 0) return -1; memset(buf, 0, sizeof(buf)); ret = read(fd, buf, sizeof(buf) - 1); close(fd); if (ret < 0) return -1; if (sscanf(buf, "%d", &deny_unknown) != 1) return -1; return deny_unknown; } hidden_def(security_deny_unknown); src/disable.c0100644 0000000 0000000 00000001142 12763246076 012156 0ustar000000000 0000000 #include #include #include #include #include #include #include "selinux_internal.h" #include "policy.h" #include #include int security_disable(void) { int fd, ret; char path[PATH_MAX]; char buf[20]; if (!selinux_mnt) { errno = ENOENT; return -1; } snprintf(path, sizeof path, "%s/disable", selinux_mnt); fd = open(path, O_WRONLY); if (fd < 0) return -1; buf[0] = '1'; buf[1] = '\0'; ret = write(fd, buf, strlen(buf)); close(fd); if (ret < 0) return -1; return 0; } hidden_def(security_disable) src/dso.h0100644 0000000 0000000 00000001210 12763246076 011341 0ustar000000000 0000000 #ifndef _SELINUX_DSO_H #define _SELINUX_DSO_H 1 #ifdef SHARED # define hidden __attribute__ ((visibility ("hidden"))) # define hidden_proto(fct) __hidden_proto (fct, fct##_internal) # define __hidden_proto(fct, internal) \ extern __typeof (fct) internal; \ extern __typeof (fct) fct __asm (#internal) hidden; # if defined(__alpha__) || defined(__mips__) # define hidden_def(fct) \ asm (".globl " #fct "\n" #fct " = " #fct "_internal"); # else # define hidden_def(fct) \ asm (".globl " #fct "\n.set " #fct ", " #fct "_internal"); #endif #else # define hidden # define hidden_proto(fct) # define hidden_def(fct) #endif #endif src/enabled.c0100644 0000000 0000000 00000002002 12763246076 012141 0ustar000000000 0000000 #include #include #include #include "selinux_internal.h" #include #include #include #include #include "policy.h" int is_selinux_enabled(void) { /* init_selinuxmnt() gets called before this function. We * will assume that if a selinux file system is mounted, then * selinux is enabled. */ return (selinux_mnt ? 1 : 0); } hidden_def(is_selinux_enabled) /* * Function: is_selinux_mls_enabled() * Return: 1 on success * 0 on failure */ int is_selinux_mls_enabled(void) { char buf[20], path[PATH_MAX]; int fd, ret, enabled = 0; if (!selinux_mnt) return enabled; snprintf(path, sizeof path, "%s/mls", selinux_mnt); fd = open(path, O_RDONLY); if (fd < 0) return enabled; memset(buf, 0, sizeof buf); do { ret = read(fd, buf, sizeof buf - 1); } while (ret < 0 && errno == EINTR); close(fd); if (ret < 0) return enabled; if (!strcmp(buf, "1")) enabled = 1; return enabled; } hidden_def(is_selinux_mls_enabled) src/fgetfilecon.c0100644 0000000 0000000 00000001602 12763246076 013041 0ustar000000000 0000000 #include #include #include #include #include #include #include "selinux_internal.h" #include "policy.h" int fgetfilecon(int fd, char ** context) { char *buf; ssize_t size; ssize_t ret; size = INITCONTEXTLEN + 1; buf = malloc(size); if (!buf) return -1; memset(buf, 0, size); ret = fgetxattr(fd, XATTR_NAME_SELINUX, buf, size - 1); if (ret < 0 && errno == ERANGE) { char *newbuf; size = fgetxattr(fd, XATTR_NAME_SELINUX, NULL, 0); if (size < 0) goto out; size++; newbuf = realloc(buf, size); if (!newbuf) goto out; buf = newbuf; memset(buf, 0, size); ret = fgetxattr(fd, XATTR_NAME_SELINUX, buf, size - 1); } out: if (ret == 0) { /* Re-map empty attribute values to errors. */ errno = EOPNOTSUPP; ret = -1; } if (ret < 0) free(buf); else *context = buf; return ret; } src/freecon.c0100644 0000000 0000000 00000000230 12763246076 012171 0ustar000000000 0000000 #include #include "selinux_internal.h" #include #include void freecon(char * con) { free(con); } hidden_def(freecon) src/fsetfilecon.c0100644 0000000 0000000 00000000456 12763246076 013063 0ustar000000000 0000000 #include #include #include #include #include #include #include "selinux_internal.h" #include "policy.h" int fsetfilecon(int fd, const char *context) { return fsetxattr(fd, XATTR_NAME_SELINUX, context, strlen(context) + 1, 0); } src/get_initial_context.c0100644 0000000 0000000 00000001577 12763246076 014623 0ustar000000000 0000000 #include #include #include #include #include #include #include #include "selinux_internal.h" #include "policy.h" #include #define SELINUX_INITCON_DIR "/initial_contexts/" int security_get_initial_context(const char * name, char ** con) { char path[PATH_MAX]; char *buf; size_t size; int fd, ret; if (!selinux_mnt) { errno = ENOENT; return -1; } snprintf(path, sizeof path, "%s%s%s", selinux_mnt, SELINUX_INITCON_DIR, name); fd = open(path, O_RDONLY); if (fd < 0) return -1; size = selinux_page_size; buf = malloc(size); if (!buf) { ret = -1; goto out; } memset(buf, 0, size); ret = read(fd, buf, size - 1); if (ret < 0) goto out2; *con = strdup(buf); if (!(*con)) { ret = -1; goto out2; } ret = 0; out2: free(buf); out: close(fd); return ret; } src/getenforce.c0100644 0000000 0000000 00000001260 12763246076 012675 0ustar000000000 0000000 #include #include #include #include #include #include #include "selinux_internal.h" #include "policy.h" #include #include int security_getenforce(void) { int fd, ret, enforce = 0; char path[PATH_MAX]; char buf[20]; if (!selinux_mnt) { errno = ENOENT; return -1; } snprintf(path, sizeof path, "%s/enforce", selinux_mnt); fd = open(path, O_RDONLY); if (fd < 0) return -1; memset(buf, 0, sizeof buf); ret = read(fd, buf, sizeof buf - 1); close(fd); if (ret < 0) return -1; if (sscanf(buf, "%d", &enforce) != 1) return -1; return enforce; } hidden_def(security_getenforce) src/getfilecon.c0100644 0000000 0000000 00000001615 12763246076 012677 0ustar000000000 0000000 #include #include #include #include "selinux_internal.h" #include #include #include #include "policy.h" int getfilecon(const char *path, char ** context) { char *buf; ssize_t size; ssize_t ret; size = INITCONTEXTLEN + 1; buf = malloc(size); if (!buf) return -1; memset(buf, 0, size); ret = getxattr(path, XATTR_NAME_SELINUX, buf, size - 1); if (ret < 0 && errno == ERANGE) { char *newbuf; size = getxattr(path, XATTR_NAME_SELINUX, NULL, 0); if (size < 0) goto out; size++; newbuf = realloc(buf, size); if (!newbuf) goto out; buf = newbuf; memset(buf, 0, size); ret = getxattr(path, XATTR_NAME_SELINUX, buf, size - 1); } out: if (ret == 0) { /* Re-map empty attribute values to errors. */ errno = EOPNOTSUPP; ret = -1; } if (ret < 0) free(buf); else *context = buf; return ret; } src/getpeercon.c0100644 0000000 0000000 00000001365 12763246076 012715 0ustar000000000 0000000 #include #include #include #include #include #include #include "selinux_internal.h" #include "policy.h" #ifndef SO_PEERSEC #define SO_PEERSEC 31 #endif int getpeercon(int fd, char ** context) { char *buf; socklen_t size; ssize_t ret; size = INITCONTEXTLEN + 1; buf = malloc(size); if (!buf) return -1; memset(buf, 0, size); ret = getsockopt(fd, SOL_SOCKET, SO_PEERSEC, buf, &size); if (ret < 0 && errno == ERANGE) { char *newbuf; newbuf = realloc(buf, size); if (!newbuf) goto out; buf = newbuf; memset(buf, 0, size); ret = getsockopt(fd, SOL_SOCKET, SO_PEERSEC, buf, &size); } out: if (ret < 0) free(buf); else *context = buf; return ret; } src/init.c0100644 0000000 0000000 00000004111 12763246076 011515 0ustar000000000 0000000 #include #include #include #include #include #include #include #include #ifdef DARWIN #include #include #else #include #endif #include #include #include "dso.h" #include "policy.h" #include "selinux_internal.h" char *selinux_mnt = NULL; int selinux_page_size = 0; static void init_selinuxmnt(void) { char buf[BUFSIZ], *p; FILE *fp=NULL; struct statfs sfbuf; int rc; char *bufp; int exists = 0; if (selinux_mnt) return; /* We check to see if the preferred mount point for selinux file * system has a selinuxfs. */ do { rc = statfs(SELINUXMNT, &sfbuf); } while (rc < 0 && errno == EINTR); if (rc == 0) { if ((uint32_t)sfbuf.f_type == (uint32_t)SELINUX_MAGIC) { selinux_mnt = strdup(SELINUXMNT); return; } } /* Drop back to detecting it the long way. */ fp = fopen("/proc/filesystems", "r"); if (!fp) return; while ((bufp = fgets(buf, sizeof buf - 1, fp)) != NULL) { if (strstr(buf, "selinuxfs")) { exists = 1; break; } } if (!exists) goto out; fclose(fp); /* At this point, the usual spot doesn't have an selinuxfs so * we look around for it */ fp = fopen("/proc/mounts", "r"); if (!fp) goto out; while ((bufp = fgets(buf, sizeof buf - 1, fp)) != NULL) { char *tmp; p = strchr(buf, ' '); if (!p) goto out; p++; tmp = strchr(p, ' '); if (!tmp) goto out; if (!strncmp(tmp + 1, "selinuxfs ", 10)) { *tmp = '\0'; break; } } /* If we found something, dup it */ if (bufp) selinux_mnt = strdup(p); out: if (fp) fclose(fp); return; } void fini_selinuxmnt(void) { free(selinux_mnt); selinux_mnt = NULL; } void set_selinuxmnt(const char *mnt) { selinux_mnt = strdup(mnt); } hidden_def(set_selinuxmnt) static void init_lib(void) __attribute__ ((constructor)); static void init_lib(void) { selinux_page_size = sysconf(_SC_PAGE_SIZE); init_selinuxmnt(); } static void fini_lib(void) __attribute__ ((destructor)); static void fini_lib(void) { fini_selinuxmnt(); } src/label.c0100644 0000000 0000000 00000006316 12763246076 011642 0ustar000000000 0000000 /* * Generalized labeling frontend for userspace object managers. * * Author : Eamon Walsh */ #include #include #include #include #include #include #include #include "callbacks.h" #include "label_internal.h" #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) typedef int (*selabel_initfunc)(struct selabel_handle *rec, const struct selinux_opt *opts, unsigned nopts); static selabel_initfunc initfuncs[] = { &selabel_file_init, NULL, NULL, NULL, &selabel_property_init, }; /* * Validation functions */ static inline int selabel_is_validate_set(const struct selinux_opt *opts, unsigned n) { while (n--) if (opts[n].type == SELABEL_OPT_VALIDATE) return !!opts[n].value; return 0; } int selabel_validate(struct selabel_handle *rec, struct selabel_lookup_rec *contexts) { int rc = 0; if (!rec->validating || contexts->validated) goto out; rc = selinux_validate(&contexts->ctx_raw); if (rc < 0) goto out; contexts->validated = 1; out: return rc; } /* * Public API */ struct selabel_handle *selabel_open(unsigned int backend, const struct selinux_opt *opts, unsigned nopts) { struct selabel_handle *rec = NULL; if (backend >= ARRAY_SIZE(initfuncs)) { errno = EINVAL; goto out; } if (initfuncs[backend] == NULL) goto out; rec = (struct selabel_handle *)malloc(sizeof(*rec)); if (!rec) goto out; memset(rec, 0, sizeof(*rec)); rec->backend = backend; rec->validating = selabel_is_validate_set(opts, nopts); if ((*initfuncs[backend])(rec, opts, nopts)) { free(rec->spec_file); free(rec); rec = NULL; } out: return rec; } static struct selabel_lookup_rec * selabel_lookup_common(struct selabel_handle *rec, const char *key, int type) { struct selabel_lookup_rec *lr; lr = rec->func_lookup(rec, key, type); if (!lr) return NULL; return lr; } int selabel_lookup(struct selabel_handle *rec, char **con, const char *key, int type) { struct selabel_lookup_rec *lr; lr = selabel_lookup_common(rec, key, type); if (!lr) return -1; *con = strdup(lr->ctx_raw); return *con ? 0 : -1; } bool selabel_partial_match(struct selabel_handle *rec, const char *key) { if (!rec->func_partial_match) { /* * If the label backend does not support partial matching, * then assume a match is possible. */ return true; } return rec->func_partial_match(rec, key); } int selabel_lookup_best_match(struct selabel_handle *rec, char **con, const char *key, const char **aliases, int type) { struct selabel_lookup_rec *lr; if (!rec->func_lookup_best_match) { errno = ENOTSUP; return -1; } lr = rec->func_lookup_best_match(rec, key, aliases, type); if (!lr) return -1; *con = strdup(lr->ctx_raw); return *con ? 0 : -1; } enum selabel_cmp_result selabel_cmp(struct selabel_handle *h1, struct selabel_handle *h2) { if (!h1->func_cmp || h1->func_cmp != h2->func_cmp) return SELABEL_INCOMPARABLE; return h1->func_cmp(h1, h2); } void selabel_close(struct selabel_handle *rec) { rec->func_close(rec); free(rec->spec_file); free(rec); } void selabel_stats(struct selabel_handle *rec) { rec->func_stats(rec); } src/label_android_property.c0100644 0000000 0000000 00000014556 12763246076 015313 0ustar000000000 0000000 /* * Property Service contexts backend for labeling Android * property keys */ #include #include #include #include #include #include #include #include "callbacks.h" #include "label_internal.h" /* A property security context specification. */ typedef struct spec { struct selabel_lookup_rec lr; /* holds contexts for lookup result */ char *property_key; /* property key string */ } spec_t; /* Our stored configuration */ struct saved_data { /* * The array of specifications is sorted for longest * prefix match */ spec_t *spec_arr; unsigned int nspec; /* total number of specifications */ }; static int cmp(const void *A, const void *B) { const struct spec *sp1 = A, *sp2 = B; if (strncmp(sp1->property_key, "*", 1) == 0) return 1; if (strncmp(sp2->property_key, "*", 1) == 0) return -1; size_t L1 = strlen(sp1->property_key); size_t L2 = strlen(sp2->property_key); return (L1 < L2) - (L1 > L2); } /* * Warn about duplicate specifications. */ static int nodups_specs(struct saved_data *data, const char *path) { int rc = 0; unsigned int ii, jj; struct spec *curr_spec, *spec_arr = data->spec_arr; for (ii = 0; ii < data->nspec; ii++) { curr_spec = &spec_arr[ii]; for (jj = ii + 1; jj < data->nspec; jj++) { if (!strcmp(spec_arr[jj].property_key, curr_spec->property_key)) { rc = -1; errno = EINVAL; if (strcmp(spec_arr[jj].lr.ctx_raw, curr_spec->lr.ctx_raw)) { selinux_log (SELINUX_ERROR, "%s: Multiple different specifications for %s (%s and %s).\n", path, curr_spec->property_key, spec_arr[jj].lr.ctx_raw, curr_spec->lr.ctx_raw); } else { selinux_log (SELINUX_ERROR, "%s: Multiple same specifications for %s.\n", path, curr_spec->property_key); } } } } return rc; } static int process_line(struct selabel_handle *rec, const char *path, char *line_buf, int pass, unsigned lineno) { int items; char *prop = NULL, *context = NULL; struct saved_data *data = (struct saved_data *)rec->data; spec_t *spec_arr = data->spec_arr; unsigned int nspec = data->nspec; const char *errbuf = NULL; items = read_spec_entries(line_buf, &errbuf, 2, &prop, &context); if (items < 0) { items = errno; selinux_log(SELINUX_ERROR, "%s: line %u error due to: %s\n", path, lineno, errbuf ?: strerror(errno)); errno = items; return -1; } if (items == 0) return items; if (items != 2) { selinux_log(SELINUX_ERROR, "%s: line %u is missing fields\n", path, lineno); free(prop); errno = EINVAL; return -1; } if (pass == 0) { free(prop); free(context); } else if (pass == 1) { /* On the second pass, process and store the specification in spec. */ spec_arr[nspec].property_key = prop; spec_arr[nspec].lr.ctx_raw = context; if (rec->validating) { if (selabel_validate(rec, &spec_arr[nspec].lr) < 0) { selinux_log(SELINUX_ERROR, "%s: line %u has invalid context %s\n", path, lineno, spec_arr[nspec].lr.ctx_raw); errno = EINVAL; return -1; } } } data->nspec = ++nspec; return 0; } static int init(struct selabel_handle *rec, const struct selinux_opt *opts, unsigned n) { struct saved_data *data = (struct saved_data *)rec->data; const char *path = NULL; FILE *fp; char line_buf[BUFSIZ]; unsigned int lineno, maxnspec, pass; int status = -1; struct stat sb; /* Process arguments */ while (n--) switch (opts[n].type) { case SELABEL_OPT_PATH: path = opts[n].value; break; } if (!path) return -1; /* Open the specification file. */ if ((fp = fopen(path, "r")) == NULL) return -1; if (fstat(fileno(fp), &sb) < 0) goto finish; errno = EINVAL; if (!S_ISREG(sb.st_mode)) goto finish; /* * Two passes of the specification file. First is to get the size. * After the first pass, the spec array is malloced to the appropriate * size. Second pass is to populate the spec array and check for * dups. */ maxnspec = UINT_MAX / sizeof(spec_t); for (pass = 0; pass < 2; pass++) { data->nspec = 0; lineno = 0; while (fgets(line_buf, sizeof(line_buf) - 1, fp) && data->nspec < maxnspec) { if (process_line(rec, path, line_buf, pass, ++lineno) != 0) goto finish; } if (pass == 1) { status = nodups_specs(data, path); if (status) goto finish; } if (pass == 0) { if (data->nspec == 0) { status = 0; goto finish; } if (NULL == (data->spec_arr = malloc(sizeof(spec_t) * data->nspec))) goto finish; memset(data->spec_arr, 0, sizeof(spec_t) * data->nspec); maxnspec = data->nspec; rewind(fp); } } qsort(data->spec_arr, data->nspec, sizeof(struct spec), cmp); status = 0; finish: fclose(fp); return status; } /* * Backend interface routines */ static void closef(struct selabel_handle *rec) { struct saved_data *data = (struct saved_data *)rec->data; struct spec *spec; unsigned int i; for (i = 0; i < data->nspec; i++) { spec = &data->spec_arr[i]; free(spec->property_key); free(spec->lr.ctx_raw); free(spec->lr.ctx_trans); } if (data->spec_arr) free(data->spec_arr); free(data); } static struct selabel_lookup_rec *lookup(struct selabel_handle *rec, const char *key, int __attribute__((unused)) type) { struct saved_data *data = (struct saved_data *)rec->data; spec_t *spec_arr = data->spec_arr; unsigned int i; struct selabel_lookup_rec *ret = NULL; if (!data->nspec) { errno = ENOENT; goto finish; } for (i = 0; i < data->nspec; i++) { if (strncmp(spec_arr[i].property_key, key, strlen(spec_arr[i].property_key)) == 0) { break; } if (strncmp(spec_arr[i].property_key, "*", 1) == 0) break; } if (i >= data->nspec) { /* No matching specification. */ errno = ENOENT; goto finish; } ret = &spec_arr[i].lr; finish: return ret; } static void stats(struct selabel_handle __attribute__((unused)) *rec) { selinux_log(SELINUX_WARNING, "'stats' functionality not implemented.\n"); } int selabel_property_init(struct selabel_handle *rec, const struct selinux_opt *opts, unsigned nopts) { struct saved_data *data; data = (struct saved_data *)malloc(sizeof(*data)); if (!data) return -1; memset(data, 0, sizeof(*data)); rec->data = data; rec->func_close = &closef; rec->func_stats = &stats; rec->func_lookup = &lookup; return init(rec, opts, nopts); } src/label_file.c0100644 0000000 0000000 00000051152 12763246076 012637 0ustar000000000 0000000 /* * File contexts backend for labeling system * * Author : Eamon Walsh * Author : Stephen Smalley */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "callbacks.h" #include "label_internal.h" #include "label_file.h" /* * Internals, mostly moved over from matchpathcon.c */ /* return the length of the text that is the stem of a file name */ static int get_stem_from_file_name(const char *const buf) { const char *tmp = strchr(buf + 1, '/'); if (!tmp) return 0; return tmp - buf; } /* find the stem of a file name, returns the index into stem_arr (or -1 if * there is no match - IE for a file in the root directory or a regex that is * too complex for us). Makes buf point to the text AFTER the stem. */ static int find_stem_from_file(struct saved_data *data, const char **buf) { int i; int stem_len = get_stem_from_file_name(*buf); if (!stem_len) return -1; for (i = 0; i < data->num_stems; i++) { if (stem_len == data->stem_arr[i].len && !strncmp(*buf, data->stem_arr[i].buf, stem_len)) { *buf += stem_len; return i; } } return -1; } /* * Warn about duplicate specifications. */ static int nodups_specs(struct saved_data *data, const char *path) { int rc = 0; unsigned int ii, jj; struct spec *curr_spec, *spec_arr = data->spec_arr; for (ii = 0; ii < data->nspec; ii++) { curr_spec = &spec_arr[ii]; for (jj = ii + 1; jj < data->nspec; jj++) { if ((!strcmp(spec_arr[jj].regex_str, curr_spec->regex_str)) && (!spec_arr[jj].mode || !curr_spec->mode || spec_arr[jj].mode == curr_spec->mode)) { rc = -1; errno = EINVAL; if (strcmp(spec_arr[jj].lr.ctx_raw, curr_spec->lr.ctx_raw)) { selinux_log (SELINUX_ERROR, "%s: Multiple different specifications for %s (%s and %s).\n", path, curr_spec->regex_str, spec_arr[jj].lr.ctx_raw, curr_spec->lr.ctx_raw); } else { selinux_log (SELINUX_ERROR, "%s: Multiple same specifications for %s.\n", path, curr_spec->regex_str); } } } } return rc; } static int load_mmap(struct selabel_handle *rec, const char *path, struct stat *sb, bool isbinary) { struct saved_data *data = (struct saved_data *)rec->data; char mmap_path[PATH_MAX + 1]; int mmapfd; int rc; struct stat mmap_stat; char *addr, *str_buf; size_t len; int *stem_map; struct mmap_area *mmap_area; uint32_t i, magic, version; uint32_t entry_len, stem_map_len, regex_array_len; if (isbinary) { len = strlen(path); if (len >= sizeof(mmap_path)) return -1; strcpy(mmap_path, path); } else { rc = snprintf(mmap_path, sizeof(mmap_path), "%s.bin", path); if (rc >= (int)sizeof(mmap_path)) return -1; } mmapfd = open(mmap_path, O_RDONLY | O_CLOEXEC); if (mmapfd < 0) return -1; rc = fstat(mmapfd, &mmap_stat); if (rc < 0) { close(mmapfd); return -1; } /* if mmap is old, ignore it */ if (mmap_stat.st_mtime < sb->st_mtime) { close(mmapfd); return -1; } /* ok, read it in... */ len = mmap_stat.st_size; len += (sysconf(_SC_PAGE_SIZE) - 1); len &= ~(sysconf(_SC_PAGE_SIZE) - 1); mmap_area = malloc(sizeof(*mmap_area)); if (!mmap_area) { close(mmapfd); return -1; } addr = mmap(NULL, len, PROT_READ, MAP_PRIVATE, mmapfd, 0); close(mmapfd); if (addr == MAP_FAILED) { free(mmap_area); perror("mmap"); return -1; } /* save where we mmap'd the file to cleanup on close() */ mmap_area->addr = mmap_area->next_addr = addr; mmap_area->len = mmap_area->next_len = len; mmap_area->next = data->mmap_areas; data->mmap_areas = mmap_area; /* check if this looks like an fcontext file */ rc = next_entry(&magic, mmap_area, sizeof(uint32_t)); if (rc < 0 || magic != SELINUX_MAGIC_COMPILED_FCONTEXT) return -1; /* check if this version is higher than we understand */ rc = next_entry(&version, mmap_area, sizeof(uint32_t)); if (rc < 0 || version > SELINUX_COMPILED_FCONTEXT_MAX_VERS) return -1; if (version >= SELINUX_COMPILED_FCONTEXT_PCRE_VERS) { len = strlen(pcre_version()); rc = next_entry(&entry_len, mmap_area, sizeof(uint32_t)); if (rc < 0) return -1; /* Check version lengths */ if (len != entry_len) return -1; /* Check if pcre version mismatch */ str_buf = malloc(entry_len + 1); if (!str_buf) return -1; rc = next_entry(str_buf, mmap_area, entry_len); if (rc < 0) { free(str_buf); return -1; } str_buf[entry_len] = '\0'; if ((strcmp(str_buf, pcre_version()) != 0)) { free(str_buf); return -1; } free(str_buf); } /* allocate the stems_data array */ rc = next_entry(&stem_map_len, mmap_area, sizeof(uint32_t)); if (rc < 0 || !stem_map_len) return -1; /* * map indexed by the stem # in the mmap file and contains the stem * number in the data stem_arr */ stem_map = calloc(stem_map_len, sizeof(*stem_map)); if (!stem_map) return -1; for (i = 0; i < stem_map_len; i++) { char *buf; uint32_t stem_len; int newid; /* the length does not inlude the nul */ rc = next_entry(&stem_len, mmap_area, sizeof(uint32_t)); if (rc < 0 || !stem_len) { rc = -1; goto err; } /* Check for stem_len wrap around. */ if (stem_len < UINT32_MAX) { buf = (char *)mmap_area->next_addr; /* Check if over-run before null check. */ rc = next_entry(NULL, mmap_area, (stem_len + 1)); if (rc < 0) goto err; if (buf[stem_len] != '\0') { rc = -1; goto err; } } else { rc = -1; goto err; } /* store the mapping between old and new */ newid = find_stem(data, buf, stem_len); if (newid < 0) { newid = store_stem(data, buf, stem_len); if (newid < 0) { rc = newid; goto err; } data->stem_arr[newid].from_mmap = 1; } stem_map[i] = newid; } /* allocate the regex array */ rc = next_entry(®ex_array_len, mmap_area, sizeof(uint32_t)); if (rc < 0 || !regex_array_len) { rc = -1; goto err; } for (i = 0; i < regex_array_len; i++) { struct spec *spec; int32_t stem_id, meta_chars; uint32_t mode = 0, prefix_len = 0; rc = grow_specs(data); if (rc < 0) goto err; spec = &data->spec_arr[data->nspec]; spec->from_mmap = 1; spec->regcomp = 1; /* Process context */ rc = next_entry(&entry_len, mmap_area, sizeof(uint32_t)); if (rc < 0 || !entry_len) { rc = -1; goto err; } str_buf = malloc(entry_len); if (!str_buf) { rc = -1; goto err; } rc = next_entry(str_buf, mmap_area, entry_len); if (rc < 0) goto err; if (str_buf[entry_len - 1] != '\0') { free(str_buf); rc = -1; goto err; } spec->lr.ctx_raw = str_buf; if (strcmp(spec->lr.ctx_raw, "<>") && rec->validating) { if (selabel_validate(rec, &spec->lr) < 0) { selinux_log(SELINUX_ERROR, "%s: context %s is invalid\n", mmap_path, spec->lr.ctx_raw); goto err; } } /* Process regex string */ rc = next_entry(&entry_len, mmap_area, sizeof(uint32_t)); if (rc < 0 || !entry_len) { rc = -1; goto err; } spec->regex_str = (char *)mmap_area->next_addr; rc = next_entry(NULL, mmap_area, entry_len); if (rc < 0) goto err; if (spec->regex_str[entry_len - 1] != '\0') { rc = -1; goto err; } /* Process mode */ if (version >= SELINUX_COMPILED_FCONTEXT_MODE) rc = next_entry(&mode, mmap_area, sizeof(uint32_t)); else rc = next_entry(&mode, mmap_area, sizeof(mode_t)); if (rc < 0) goto err; spec->mode = mode; /* map the stem id from the mmap file to the data->stem_arr */ rc = next_entry(&stem_id, mmap_area, sizeof(int32_t)); if (rc < 0) goto err; if (stem_id < 0 || stem_id >= (int32_t)stem_map_len) spec->stem_id = -1; else spec->stem_id = stem_map[stem_id]; /* retrieve the hasMetaChars bit */ rc = next_entry(&meta_chars, mmap_area, sizeof(uint32_t)); if (rc < 0) goto err; spec->hasMetaChars = meta_chars; /* and prefix length for use by selabel_lookup_best_match */ if (version >= SELINUX_COMPILED_FCONTEXT_PREFIX_LEN) { rc = next_entry(&prefix_len, mmap_area, sizeof(uint32_t)); if (rc < 0) goto err; spec->prefix_len = prefix_len; } /* Process regex and study_data entries */ rc = next_entry(&entry_len, mmap_area, sizeof(uint32_t)); if (rc < 0 || !entry_len) { rc = -1; goto err; } spec->regex = (pcre *)mmap_area->next_addr; rc = next_entry(NULL, mmap_area, entry_len); if (rc < 0) goto err; /* Check that regex lengths match. pcre_fullinfo() * also validates its magic number. */ rc = pcre_fullinfo(spec->regex, NULL, PCRE_INFO_SIZE, &len); if (rc < 0 || len != entry_len) { rc = -1; goto err; } rc = next_entry(&entry_len, mmap_area, sizeof(uint32_t)); if (rc < 0 || !entry_len) { rc = -1; goto err; } spec->lsd.study_data = (void *)mmap_area->next_addr; spec->lsd.flags |= PCRE_EXTRA_STUDY_DATA; rc = next_entry(NULL, mmap_area, entry_len); if (rc < 0) goto err; /* Check that study data lengths match. */ rc = pcre_fullinfo(spec->regex, &spec->lsd, PCRE_INFO_STUDYSIZE, &len); if (rc < 0 || len != entry_len) { rc = -1; goto err; } data->nspec++; } /* win */ rc = 0; err: free(stem_map); return rc; } static int process_file(const char *path, const char *suffix, struct selabel_handle *rec, const char *prefix) { FILE *fp; struct stat sb; unsigned int lineno; size_t line_len = 0; char *line_buf = NULL; int rc; char stack_path[PATH_MAX + 1]; bool isbinary = false; uint32_t magic; /* append the path suffix if we have one */ if (suffix) { rc = snprintf(stack_path, sizeof(stack_path), "%s.%s", path, suffix); if (rc >= (int)sizeof(stack_path)) { errno = ENAMETOOLONG; return -1; } path = stack_path; } /* Open the specification file. */ fp = fopen(path, "r"); if (fp) { if (fstat(fileno(fp), &sb) < 0) return -1; if (!S_ISREG(sb.st_mode)) { errno = EINVAL; return -1; } if (fread(&magic, sizeof magic, 1, fp) != 1) { errno = EINVAL; fclose(fp); return -1; } if (magic == SELINUX_MAGIC_COMPILED_FCONTEXT) { /* file_contexts.bin format */ fclose(fp); fp = NULL; isbinary = true; } else { rewind(fp); } } else { /* * Text file does not exist, so clear the timestamp * so that we will always pass the timestamp comparison * with the bin file in load_mmap(). */ sb.st_mtime = 0; } rc = load_mmap(rec, path, &sb, isbinary); if (rc == 0) goto out; if (!fp) return -1; /* no text or bin file */ /* * Then do detailed validation of the input and fill the spec array */ lineno = 0; rc = 0; while (getline(&line_buf, &line_len, fp) > 0) { rc = process_line(rec, path, prefix, line_buf, ++lineno); if (rc) goto out; } out: free(line_buf); if (fp) fclose(fp); return rc; } static void closef(struct selabel_handle *rec); static int init(struct selabel_handle *rec, const struct selinux_opt *opts, unsigned n) { struct saved_data *data = (struct saved_data *)rec->data; const char *path = NULL; const char *prefix = NULL; int status = -1, baseonly = 0; /* Process arguments */ while (n--) switch(opts[n].type) { case SELABEL_OPT_PATH: path = opts[n].value; break; case SELABEL_OPT_SUBSET: prefix = opts[n].value; break; case SELABEL_OPT_BASEONLY: baseonly = !!opts[n].value; break; } rec->spec_file = strdup(path); /* * The do detailed validation of the input and fill the spec array */ status = process_file(path, NULL, rec, prefix); if (status) goto finish; if (rec->validating) { status = nodups_specs(data, path); if (status) goto finish; } if (!baseonly) { status = process_file(path, "homedirs", rec, prefix); if (status && errno != ENOENT) goto finish; status = process_file(path, "local", rec, prefix); if (status && errno != ENOENT) goto finish; } status = sort_specs(data); finish: if (status) closef(rec); return status; } /* * Backend interface routines */ static void closef(struct selabel_handle *rec) { struct saved_data *data = (struct saved_data *)rec->data; struct mmap_area *area, *last_area; struct spec *spec; struct stem *stem; unsigned int i; for (i = 0; i < data->nspec; i++) { spec = &data->spec_arr[i]; free(spec->lr.ctx_trans); free(spec->lr.ctx_raw); if (spec->from_mmap) continue; free(spec->regex_str); free(spec->type_str); if (spec->regcomp) { pcre_free(spec->regex); pcre_free_study(spec->sd); } } for (i = 0; i < (unsigned int)data->num_stems; i++) { stem = &data->stem_arr[i]; if (stem->from_mmap) continue; free(stem->buf); } if (data->spec_arr) free(data->spec_arr); if (data->stem_arr) free(data->stem_arr); area = data->mmap_areas; while (area) { munmap(area->addr, area->len); last_area = area; area = area->next; free(last_area); } free(data); } static struct spec *lookup_common(struct selabel_handle *rec, const char *key, int type, bool partial) { struct saved_data *data = (struct saved_data *)rec->data; struct spec *spec_arr = data->spec_arr; int i, rc, file_stem, pcre_options = 0; mode_t mode = (mode_t)type; const char *buf; struct spec *ret = NULL; char *clean_key = NULL; const char *prev_slash, *next_slash; unsigned int sofar = 0; if (!data->nspec) { errno = ENOENT; goto finish; } /* Remove duplicate slashes */ if ((next_slash = strstr(key, "//"))) { clean_key = (char *) malloc(strlen(key) + 1); if (!clean_key) goto finish; prev_slash = key; while (next_slash) { memcpy(clean_key + sofar, prev_slash, next_slash - prev_slash); sofar += next_slash - prev_slash; prev_slash = next_slash + 1; next_slash = strstr(prev_slash, "//"); } strcpy(clean_key + sofar, prev_slash); key = clean_key; } buf = key; file_stem = find_stem_from_file(data, &buf); mode &= S_IFMT; if (partial) pcre_options |= PCRE_PARTIAL_SOFT; /* * Check for matching specifications in reverse order, so that * the last matching specification is used. */ for (i = data->nspec - 1; i >= 0; i--) { struct spec *spec = &spec_arr[i]; /* if the spec in question matches no stem or has the same * stem as the file AND if the spec in question has no mode * specified or if the mode matches the file mode then we do * a regex check */ if ((spec->stem_id == -1 || spec->stem_id == file_stem) && (!mode || !spec->mode || mode == spec->mode)) { if (compile_regex(data, spec, NULL) < 0) goto finish; if (spec->stem_id == -1) rc = pcre_exec(spec->regex, get_pcre_extra(spec), key, strlen(key), 0, pcre_options, NULL, 0); else rc = pcre_exec(spec->regex, get_pcre_extra(spec), buf, strlen(buf), 0, pcre_options, NULL, 0); if (rc == 0) { spec->matches++; break; } else if (partial && rc == PCRE_ERROR_PARTIAL) break; if (rc == PCRE_ERROR_NOMATCH) continue; errno = ENOENT; /* else it's an error */ goto finish; } } if (i < 0 || strcmp(spec_arr[i].lr.ctx_raw, "<>") == 0) { /* No matching specification. */ errno = ENOENT; goto finish; } errno = 0; ret = &spec_arr[i]; finish: free(clean_key); return ret; } static struct selabel_lookup_rec *lookup(struct selabel_handle *rec, const char *key, int type) { struct spec *spec; spec = lookup_common(rec, key, type, false); if (spec) return &spec->lr; return NULL; } static bool partial_match(struct selabel_handle *rec, const char *key) { return lookup_common(rec, key, 0, true) ? true : false; } static struct selabel_lookup_rec *lookup_best_match(struct selabel_handle *rec, const char *key, const char **aliases, int type) { size_t n, i; int best = -1; struct spec **specs; size_t prefix_len = 0; struct selabel_lookup_rec *lr = NULL; if (!aliases || !aliases[0]) return lookup(rec, key, type); for (n = 0; aliases[n]; n++) ; specs = calloc(n+1, sizeof(struct spec *)); if (!specs) return NULL; specs[0] = lookup_common(rec, key, type, false); if (specs[0]) { if (!specs[0]->hasMetaChars) { /* exact match on key */ lr = &specs[0]->lr; goto out; } best = 0; prefix_len = specs[0]->prefix_len; } for (i = 1; i <= n; i++) { specs[i] = lookup_common(rec, aliases[i-1], type, false); if (specs[i]) { if (!specs[i]->hasMetaChars) { /* exact match on alias */ lr = &specs[i]->lr; goto out; } if (specs[i]->prefix_len > prefix_len) { best = i; prefix_len = specs[i]->prefix_len; } } } if (best >= 0) { /* longest fixed prefix match on key or alias */ lr = &specs[best]->lr; } else { errno = ENOENT; } out: free(specs); return lr; } static enum selabel_cmp_result incomp(struct spec *spec1, struct spec *spec2, const char *reason, int i, int j) { selinux_log(SELINUX_INFO, "selabel_cmp: mismatched %s on entry %d: (%s, %x, %s) vs entry %d: (%s, %x, %s)\n", reason, i, spec1->regex_str, spec1->mode, spec1->lr.ctx_raw, j, spec2->regex_str, spec2->mode, spec2->lr.ctx_raw); return SELABEL_INCOMPARABLE; } static enum selabel_cmp_result cmp(struct selabel_handle *h1, struct selabel_handle *h2) { struct saved_data *data1 = (struct saved_data *)h1->data; struct saved_data *data2 = (struct saved_data *)h2->data; unsigned int i, nspec1 = data1->nspec, j, nspec2 = data2->nspec; struct spec *spec_arr1 = data1->spec_arr, *spec_arr2 = data2->spec_arr; struct stem *stem_arr1 = data1->stem_arr, *stem_arr2 = data2->stem_arr; bool skipped1 = false, skipped2 = false; i = 0; j = 0; while (i < nspec1 && j < nspec2) { struct spec *spec1 = &spec_arr1[i]; struct spec *spec2 = &spec_arr2[j]; /* * Because sort_specs() moves exact pathnames to the * end, we might need to skip over additional regex * entries that only exist in one of the configurations. */ if (!spec1->hasMetaChars && spec2->hasMetaChars) { j++; skipped2 = true; continue; } if (spec1->hasMetaChars && !spec2->hasMetaChars) { i++; skipped1 = true; continue; } if (spec1->regcomp && spec2->regcomp) { size_t len1, len2; int rc; rc = pcre_fullinfo(spec1->regex, NULL, PCRE_INFO_SIZE, &len1); assert(rc == 0); rc = pcre_fullinfo(spec2->regex, NULL, PCRE_INFO_SIZE, &len2); assert(rc == 0); if (len1 != len2 || memcmp(spec1->regex, spec2->regex, len1)) return incomp(spec1, spec2, "regex", i, j); } else { if (strcmp(spec1->regex_str, spec2->regex_str)) return incomp(spec1, spec2, "regex_str", i, j); } if (spec1->mode != spec2->mode) return incomp(spec1, spec2, "mode", i, j); if (spec1->stem_id == -1 && spec2->stem_id != -1) return incomp(spec1, spec2, "stem_id", i, j); if (spec2->stem_id == -1 && spec1->stem_id != -1) return incomp(spec1, spec2, "stem_id", i, j); if (spec1->stem_id != -1 && spec2->stem_id != -1) { struct stem *stem1 = &stem_arr1[spec1->stem_id]; struct stem *stem2 = &stem_arr2[spec2->stem_id]; if (stem1->len != stem2->len || strncmp(stem1->buf, stem2->buf, stem1->len)) return incomp(spec1, spec2, "stem", i, j); } if (strcmp(spec1->lr.ctx_raw, spec2->lr.ctx_raw)) return incomp(spec1, spec2, "ctx_raw", i, j); i++; j++; } if ((skipped1 || i < nspec1) && !skipped2) return SELABEL_SUPERSET; if ((skipped2 || j < nspec2) && !skipped1) return SELABEL_SUBSET; if (skipped1 && skipped2) return SELABEL_INCOMPARABLE; return SELABEL_EQUAL; } static void stats(struct selabel_handle *rec) { struct saved_data *data = (struct saved_data *)rec->data; unsigned int i, nspec = data->nspec; struct spec *spec_arr = data->spec_arr; for (i = 0; i < nspec; i++) { if (spec_arr[i].matches == 0) { if (spec_arr[i].type_str) { selinux_log(SELINUX_WARNING, "Warning! No matches for (%s, %s, %s)\n", spec_arr[i].regex_str, spec_arr[i].type_str, spec_arr[i].lr.ctx_raw); } else { selinux_log(SELINUX_WARNING, "Warning! No matches for (%s, %s)\n", spec_arr[i].regex_str, spec_arr[i].lr.ctx_raw); } } } } int selabel_file_init(struct selabel_handle *rec, const struct selinux_opt *opts, unsigned nopts) { struct saved_data *data; data = (struct saved_data *)malloc(sizeof(*data)); if (!data) return -1; memset(data, 0, sizeof(*data)); rec->data = data; rec->func_close = &closef; rec->func_stats = &stats; rec->func_lookup = &lookup; rec->func_partial_match = &partial_match; rec->func_lookup_best_match = &lookup_best_match; rec->func_cmp = &cmp; return init(rec, opts, nopts); } src/label_file.h0100644 0000000 0000000 00000026120 12763246076 012641 0ustar000000000 0000000 #ifndef _SELABEL_FILE_H_ #define _SELABEL_FILE_H_ #include #include #include #include "callbacks.h" #include "label_internal.h" #define SELINUX_MAGIC_COMPILED_FCONTEXT 0xf97cff8a /* Version specific changes */ #define SELINUX_COMPILED_FCONTEXT_NOPCRE_VERS 1 #define SELINUX_COMPILED_FCONTEXT_PCRE_VERS 2 #define SELINUX_COMPILED_FCONTEXT_MODE 3 #define SELINUX_COMPILED_FCONTEXT_PREFIX_LEN 4 #define SELINUX_COMPILED_FCONTEXT_MAX_VERS SELINUX_COMPILED_FCONTEXT_PREFIX_LEN /* Prior to version 8.20, libpcre did not have pcre_free_study() */ #if (PCRE_MAJOR < 8 || (PCRE_MAJOR == 8 && PCRE_MINOR < 20)) #define pcre_free_study pcre_free #endif /* A file security context specification. */ struct spec { struct selabel_lookup_rec lr; /* holds contexts for lookup result */ char *regex_str; /* regular expession string for diagnostics */ char *type_str; /* type string for diagnostic messages */ pcre *regex; /* compiled regular expression */ union { pcre_extra *sd; /* pointer to extra compiled stuff */ pcre_extra lsd; /* used to hold the mmap'd version */ }; mode_t mode; /* mode format value */ int matches; /* number of matching pathnames */ int stem_id; /* indicates which stem-compression item */ char hasMetaChars; /* regular expression has meta-chars */ char regcomp; /* regex_str has been compiled to regex */ char from_mmap; /* this spec is from an mmap of the data */ size_t prefix_len; /* length of fixed path prefix */ }; /* A regular expression stem */ struct stem { char *buf; int len; char from_mmap; }; /* Where we map the file in during selabel_open() */ struct mmap_area { void *addr; /* Start addr + len used to release memory at close */ size_t len; void *next_addr; /* Incremented by next_entry() */ size_t next_len; /* Decremented by next_entry() */ struct mmap_area *next; }; /* Our stored configuration */ struct saved_data { /* * The array of specifications, initially in the same order as in * the specification file. Sorting occurs based on hasMetaChars. */ struct spec *spec_arr; unsigned int nspec; unsigned int alloc_specs; /* * The array of regular expression stems. */ struct stem *stem_arr; int num_stems; int alloc_stems; struct mmap_area *mmap_areas; }; static inline pcre_extra *get_pcre_extra(struct spec *spec) { if (spec->from_mmap) return &spec->lsd; else return spec->sd; } static inline mode_t string_to_mode(char *mode) { size_t len; if (!mode) return 0; len = strlen(mode); if (mode[0] != '-' || len != 2) return -1; switch (mode[1]) { case 'b': return S_IFBLK; case 'c': return S_IFCHR; case 'd': return S_IFDIR; case 'p': return S_IFIFO; case 'l': return S_IFLNK; case 's': return S_IFSOCK; case '-': return S_IFREG; default: return -1; } /* impossible to get here */ return 0; } static inline int grow_specs(struct saved_data *data) { struct spec *specs; size_t new_specs, total_specs; if (data->nspec < data->alloc_specs) return 0; new_specs = data->nspec + 16; total_specs = data->nspec + new_specs; specs = realloc(data->spec_arr, total_specs * sizeof(*specs)); if (!specs) { perror("realloc"); return -1; } /* blank the new entries */ memset(&specs[data->nspec], 0, new_specs * sizeof(*specs)); data->spec_arr = specs; data->alloc_specs = total_specs; return 0; } /* Determine if the regular expression specification has any meta characters. */ static inline void spec_hasMetaChars(struct spec *spec) { char *c; int len; char *end; c = spec->regex_str; len = strlen(spec->regex_str); end = c + len; spec->hasMetaChars = 0; spec->prefix_len = len; /* Look at each character in the RE specification string for a * meta character. Return when any meta character reached. */ while (c < end) { switch (*c) { case '.': case '^': case '$': case '?': case '*': case '+': case '|': case '[': case '(': case '{': spec->hasMetaChars = 1; spec->prefix_len = c - spec->regex_str; return; case '\\': /* skip the next character */ c++; break; default: break; } c++; } } /* Move exact pathname specifications to the end. */ static inline int sort_specs(struct saved_data *data) { struct spec *spec_copy; struct spec spec; unsigned int i; int front, back; size_t len = sizeof(*spec_copy); spec_copy = malloc(len * data->nspec); if (!spec_copy) return -1; /* first move the exact pathnames to the back */ front = 0; back = data->nspec - 1; for (i = 0; i < data->nspec; i++) { if (data->spec_arr[i].hasMetaChars) memcpy(&spec_copy[front++], &data->spec_arr[i], len); else memcpy(&spec_copy[back--], &data->spec_arr[i], len); } /* * now the exact pathnames are at the end, but they are in the reverse * order. Since 'front' is now the first of the 'exact' we can run * that part of the array switching the front and back element. */ back = data->nspec - 1; while (front < back) { /* save the front */ memcpy(&spec, &spec_copy[front], len); /* move the back to the front */ memcpy(&spec_copy[front], &spec_copy[back], len); /* put the old front in the back */ memcpy(&spec_copy[back], &spec, len); front++; back--; } free(data->spec_arr); data->spec_arr = spec_copy; return 0; } /* Return the length of the text that can be considered the stem, returns 0 * if there is no identifiable stem */ static inline int get_stem_from_spec(const char *const buf) { const char *tmp = strchr(buf + 1, '/'); const char *ind; if (!tmp) return 0; for (ind = buf; ind < tmp; ind++) { if (strchr(".^$?*+|[({", (int)*ind)) return 0; } return tmp - buf; } /* * return the stemid given a string and a length */ static inline int find_stem(struct saved_data *data, const char *buf, int stem_len) { int i; for (i = 0; i < data->num_stems; i++) { if (stem_len == data->stem_arr[i].len && !strncmp(buf, data->stem_arr[i].buf, stem_len)) return i; } return -1; } /* returns the index of the new stored object */ static inline int store_stem(struct saved_data *data, char *buf, int stem_len) { int num = data->num_stems; if (data->alloc_stems == num) { struct stem *tmp_arr; data->alloc_stems = data->alloc_stems * 2 + 16; tmp_arr = realloc(data->stem_arr, sizeof(*tmp_arr) * data->alloc_stems); if (!tmp_arr) return -1; data->stem_arr = tmp_arr; } data->stem_arr[num].len = stem_len; data->stem_arr[num].buf = buf; data->stem_arr[num].from_mmap = 0; data->num_stems++; return num; } /* find the stem of a file spec, returns the index into stem_arr for a new * or existing stem, (or -1 if there is no possible stem - IE for a file in * the root directory or a regex that is too complex for us). */ static inline int find_stem_from_spec(struct saved_data *data, const char *buf) { int stem_len = get_stem_from_spec(buf); int stemid; char *stem; if (!stem_len) return -1; stemid = find_stem(data, buf, stem_len); if (stemid >= 0) return stemid; /* not found, allocate a new one */ stem = strndup(buf, stem_len); if (!stem) return -1; return store_stem(data, stem, stem_len); } /* This will always check for buffer over-runs and either read the next entry * if buf != NULL or skip over the entry (as these areas are mapped in the * current buffer). */ static inline int next_entry(void *buf, struct mmap_area *fp, size_t bytes) { if (bytes > fp->next_len) return -1; if (buf) memcpy(buf, fp->next_addr, bytes); fp->next_addr = (char *)fp->next_addr + bytes; fp->next_len -= bytes; return 0; } static inline int compile_regex(struct saved_data *data, struct spec *spec, const char **errbuf) { const char *tmperrbuf; char *reg_buf, *anchored_regex, *cp; struct stem *stem_arr = data->stem_arr; size_t len; int erroff; if (spec->regcomp) return 0; /* already done */ /* Skip the fixed stem. */ reg_buf = spec->regex_str; if (spec->stem_id >= 0) reg_buf += stem_arr[spec->stem_id].len; /* Anchor the regular expression. */ len = strlen(reg_buf); cp = anchored_regex = malloc(len + 3); if (!anchored_regex) return -1; /* Create ^...$ regexp. */ *cp++ = '^'; memcpy(cp, reg_buf, len); cp += len; *cp++ = '$'; *cp = '\0'; /* Compile the regular expression. */ spec->regex = pcre_compile(anchored_regex, PCRE_DOTALL, &tmperrbuf, &erroff, NULL); free(anchored_regex); if (!spec->regex) { if (errbuf) *errbuf = tmperrbuf; return -1; } spec->sd = pcre_study(spec->regex, 0, &tmperrbuf); if (!spec->sd && tmperrbuf) { if (errbuf) *errbuf = tmperrbuf; return -1; } /* Done. */ spec->regcomp = 1; return 0; } /* This service is used by label_file.c process_file() and * utils/sefcontext_compile.c */ static inline int process_line(struct selabel_handle *rec, const char *path, const char *prefix, char *line_buf, unsigned lineno) { int items, len, rc; char *regex = NULL, *type = NULL, *context = NULL; struct saved_data *data = (struct saved_data *)rec->data; struct spec *spec_arr; unsigned int nspec = data->nspec; const char *errbuf = NULL; items = read_spec_entries(line_buf, &errbuf, 3, ®ex, &type, &context); if (items < 0) { rc = errno; selinux_log(SELINUX_ERROR, "%s: line %u error due to: %s\n", path, lineno, errbuf ?: strerror(errno)); errno = rc; return -1; } if (items == 0) return items; if (items < 2) { selinux_log(SELINUX_ERROR, "%s: line %u is missing fields\n", path, lineno); if (items == 1) free(regex); errno = EINVAL; return -1; } else if (items == 2) { /* The type field is optional. */ context = type; type = 0; } len = get_stem_from_spec(regex); if (len && prefix && strncmp(prefix, regex, len)) { /* Stem of regex does not match requested prefix, discard. */ free(regex); free(type); free(context); return 0; } rc = grow_specs(data); if (rc) return rc; spec_arr = data->spec_arr; /* process and store the specification in spec. */ spec_arr[nspec].stem_id = find_stem_from_spec(data, regex); spec_arr[nspec].regex_str = regex; spec_arr[nspec].type_str = type; spec_arr[nspec].mode = 0; spec_arr[nspec].lr.ctx_raw = context; /* * bump data->nspecs to cause closef() to cover it in its free * but do not bump nspec since it's used below. */ data->nspec++; if (rec->validating && compile_regex(data, &spec_arr[nspec], &errbuf)) { selinux_log(SELINUX_ERROR, "%s: line %u has invalid regex %s: %s\n", path, lineno, regex, (errbuf ? errbuf : "out of memory")); errno = EINVAL; return -1; } if (type) { mode_t mode = string_to_mode(type); if (mode == (mode_t)-1) { selinux_log(SELINUX_ERROR, "%s: line %u has invalid file type %s\n", path, lineno, type); errno = EINVAL; return -1; } spec_arr[nspec].mode = mode; } /* Determine if specification has * any meta characters in the RE */ spec_hasMetaChars(&spec_arr[nspec]); if (strcmp(context, "<>") && rec->validating) { if (selabel_validate(rec, &spec_arr[nspec].lr) < 0) { selinux_log(SELINUX_ERROR, "%s: line %u has invalid context %s\n", path, lineno, spec_arr[nspec].lr.ctx_raw); errno = EINVAL; return -1; } } return 0; } #endif /* _SELABEL_FILE_H_ */ src/label_internal.h0100644 0000000 0000000 00000004776 12763246076 013553 0ustar000000000 0000000 /* * This file describes the internal interface used by the labeler * for calling the user-supplied memory allocation, validation, * and locking routine. * * Author : Eamon Walsh */ #ifndef _SELABEL_INTERNAL_H_ #define _SELABEL_INTERNAL_H_ #include #include #include #include #include "dso.h" /* * Installed backends */ int selabel_file_init(struct selabel_handle *rec, const struct selinux_opt *opts, unsigned nopts) hidden; int selabel_media_init(struct selabel_handle *rec, const struct selinux_opt *opts, unsigned nopts) hidden; int selabel_x_init(struct selabel_handle *rec, const struct selinux_opt *opts, unsigned nopts) hidden; int selabel_db_init(struct selabel_handle *rec, const struct selinux_opt *opts, unsigned nopts) hidden; int selabel_property_init(struct selabel_handle *rec, const struct selinux_opt *opts, unsigned nopts) hidden; /* * Labeling internal structures */ struct selabel_sub { char *src; int slen; char *dst; struct selabel_sub *next; }; struct selabel_lookup_rec { char * ctx_raw; char * ctx_trans; int validated; }; struct selabel_handle { /* arguments that were passed to selabel_open */ unsigned int backend; int validating; /* labeling operations */ struct selabel_lookup_rec *(*func_lookup) (struct selabel_handle *h, const char *key, int type); void (*func_close) (struct selabel_handle *h); void (*func_stats) (struct selabel_handle *h); bool (*func_partial_match) (struct selabel_handle *h, const char *key); struct selabel_lookup_rec *(*func_lookup_best_match) (struct selabel_handle *h, const char *key, const char **aliases, int type); enum selabel_cmp_result (*func_cmp)(struct selabel_handle *h1, struct selabel_handle *h2); /* supports backend-specific state information */ void *data; /* * The main spec file used. Note for file contexts the local and/or * homedirs could also have been used to resolve a context. */ char *spec_file; /* substitution support */ struct selabel_sub *dist_subs; struct selabel_sub *subs; }; /* * Validation function */ extern int selabel_validate(struct selabel_handle *rec, struct selabel_lookup_rec *contexts) hidden; /* * The read_spec_entries function may be used to * replace sscanf to read entries from spec files. */ extern int read_spec_entries(char *line_buf, const char **errbuf, int num_args, ...); #endif /* _SELABEL_INTERNAL_H_ */ src/label_support.c0100644 0000000 0000000 00000005270 12763246076 013434 0ustar000000000 0000000 /* * This file contains helper functions for labeling support. * * Author : Richard Haines */ #include #include #include #include #include #include "label_internal.h" /* * The read_spec_entries and read_spec_entry functions may be used to * replace sscanf to read entries from spec files. The file and * property services now use these. */ /* * Read an entry from a spec file (e.g. file_contexts) * entry - Buffer to allocate for the entry. * ptr - current location of the line to be processed. * returns - 0 on success and *entry is set to be a null * terminated value. On Error it returns -1 and errno will be set. * */ static inline int read_spec_entry(char **entry, char **ptr, int *len, const char **errbuf) { *entry = NULL; char *tmp_buf = NULL; while (isspace(**ptr) && **ptr != '\0') (*ptr)++; tmp_buf = *ptr; *len = 0; while (!isspace(**ptr) && **ptr != '\0') { if (!isascii(**ptr)) { errno = EINVAL; *errbuf = "Non-ASCII characters found"; return -1; } (*ptr)++; (*len)++; } if (*len) { *entry = strndup(tmp_buf, *len); if (!*entry) return -1; } return 0; } /* * line_buf - Buffer containing the spec entries . * errbuf - Double pointer used for passing back specific error messages. * num_args - The number of spec parameter entries to process. * ... - A 'char **spec_entry' for each parameter. * returns - The number of items processed. On error, it returns -1 with errno * set and may set errbuf to a specific error message. * * This function calls read_spec_entry() to do the actual string processing. * As such, can return anything from that function as well. */ int hidden read_spec_entries(char *line_buf, const char **errbuf, int num_args, ...) { char **spec_entry, *buf_p; int len, rc, items, entry_len = 0; va_list ap; *errbuf = NULL; len = strlen(line_buf); if (line_buf[len - 1] == '\n') line_buf[len - 1] = '\0'; else /* Handle case if line not \n terminated by bumping * the len for the check below (as the line is NUL * terminated by getline(3)) */ len++; buf_p = line_buf; while (isspace(*buf_p)) buf_p++; /* Skip comment lines and empty lines. */ if (*buf_p == '#' || *buf_p == '\0') return 0; /* Process the spec file entries */ va_start(ap, num_args); items = 0; while (items < num_args) { spec_entry = va_arg(ap, char **); if (len - 1 == buf_p - line_buf) { va_end(ap); return items; } rc = read_spec_entry(spec_entry, &buf_p, &entry_len, errbuf); if (rc < 0) { va_end(ap); return rc; } if (entry_len) items++; } va_end(ap); return items; } src/lgetfilecon.c0100644 0000000 0000000 00000001621 12763246076 013050 0ustar000000000 0000000 #include #include #include #include #include #include #include "selinux_internal.h" #include "policy.h" int lgetfilecon(const char *path, char ** context) { char *buf; ssize_t size; ssize_t ret; size = INITCONTEXTLEN + 1; buf = malloc(size); if (!buf) return -1; memset(buf, 0, size); ret = lgetxattr(path, XATTR_NAME_SELINUX, buf, size - 1); if (ret < 0 && errno == ERANGE) { char *newbuf; size = lgetxattr(path, XATTR_NAME_SELINUX, NULL, 0); if (size < 0) goto out; size++; newbuf = realloc(buf, size); if (!newbuf) goto out; buf = newbuf; memset(buf, 0, size); ret = lgetxattr(path, XATTR_NAME_SELINUX, buf, size - 1); } out: if (ret == 0) { /* Re-map empty attribute values to errors. */ errno = EOPNOTSUPP; ret = -1; } if (ret < 0) free(buf); else *context = buf; return ret; } src/load_policy.c0100644 0000000 0000000 00000001332 12763246076 013052 0ustar000000000 0000000 #include #include #include #include #include #include #include #include #include #include #include #include #include "selinux_internal.h" #include #include "policy.h" #include #include "callbacks.h" int security_load_policy(void *data, size_t len) { char path[PATH_MAX]; int fd, ret; if (!selinux_mnt) { errno = ENOENT; return -1; } snprintf(path, sizeof path, "%s/load", selinux_mnt); fd = open(path, O_RDWR); if (fd < 0) return -1; ret = write(fd, data, len); close(fd); if (ret < 0) return -1; return 0; } hidden_def(security_load_policy) src/lsetfilecon.c0100644 0000000 0000000 00000000472 12763246076 013067 0ustar000000000 0000000 #include #include #include #include #include #include #include "selinux_internal.h" #include "policy.h" int lsetfilecon(const char *path, const char *context) { return lsetxattr(path, XATTR_NAME_SELINUX, context, strlen(context) + 1, 0); } src/mapping.c0100644 0000000 0000000 00000010413 12763246076 012207 0ustar000000000 0000000 /* * Class and permission mappings. */ #include #include #include #include #include #include #include "mapping.h" /* * Class and permission mappings */ struct selinux_mapping { security_class_t value; /* real, kernel value */ unsigned num_perms; access_vector_t perms[sizeof(access_vector_t) * 8]; }; static struct selinux_mapping *current_mapping = NULL; static security_class_t current_mapping_size = 0; /* * Mapping setting function */ int selinux_set_mapping(struct security_class_mapping *map) { size_t size = sizeof(struct selinux_mapping); security_class_t i, j; unsigned k; free(current_mapping); current_mapping = NULL; current_mapping_size = 0; if (avc_reset() < 0) goto err; /* Find number of classes in the input mapping */ if (!map) { errno = EINVAL; goto err; } i = 0; while (map[i].name) i++; /* Allocate space for the class records, plus one for class zero */ current_mapping = (struct selinux_mapping *)calloc(++i, size); if (!current_mapping) goto err; /* Store the raw class and permission values */ j = 0; while (map[j].name) { struct security_class_mapping *p_in = map + (j++); struct selinux_mapping *p_out = current_mapping + j; p_out->value = string_to_security_class(p_in->name); if (!p_out->value) goto err2; k = 0; while (p_in->perms[k]) { /* An empty permission string skips ahead */ if (!*p_in->perms[k]) { k++; continue; } p_out->perms[k] = string_to_av_perm(p_out->value, p_in->perms[k]); if (!p_out->perms[k]) goto err2; k++; } p_out->num_perms = k; } /* Set the mapping size here so the above lookups are "raw" */ current_mapping_size = i; return 0; err2: free(current_mapping); current_mapping = NULL; current_mapping_size = 0; err: return -1; } /* * Get real, kernel values from mapped values */ security_class_t unmap_class(security_class_t tclass) { if (tclass < current_mapping_size) return current_mapping[tclass].value; /* If here no mapping set or the class requested is not valid. */ if (current_mapping_size != 0) { errno = EINVAL; return 0; } else return tclass; } access_vector_t unmap_perm(security_class_t tclass, access_vector_t tperm) { if (tclass < current_mapping_size) { unsigned i; access_vector_t kperm = 0; for (i=0; iallowed & current_mapping[tclass].perms[i]) result |= 1<allowed = result; for (i=0, result=0; idecided & current_mapping[tclass].perms[i]) result |= 1<decided = result; for (i=0, result=0; iauditallow & current_mapping[tclass].perms[i]) result |= 1<auditallow = result; for (i=0, result=0; iauditdeny & current_mapping[tclass].perms[i]) result |= 1<auditdeny = result; } } src/mapping.h0100644 0000000 0000000 00000001665 12763246076 012225 0ustar000000000 0000000 /* * This file describes the class and permission mappings used to * hide the kernel numbers from userspace by allowing userspace object * managers to specify a list of classes and permissions. */ #ifndef _SELINUX_MAPPING_H_ #define _SELINUX_MAPPING_H_ #include /* * Get real, kernel values from mapped values */ extern security_class_t unmap_class(security_class_t tclass); extern access_vector_t unmap_perm(security_class_t tclass, access_vector_t tperm); /* * Get mapped values from real, kernel values */ extern security_class_t map_class(security_class_t kclass); extern access_vector_t map_perm(security_class_t tclass, access_vector_t kperm); extern void map_decision(security_class_t tclass, struct av_decision *avd); /*mapping is not used for embedded build*/ #ifdef DISABLE_AVC #define unmap_perm(x,y) y #define unmap_class(x) x #define map_decision(x,y) #endif #endif /* _SELINUX_MAPPING_H_ */ src/policy.h0100644 0000000 0000000 00000001274 12763246076 012065 0ustar000000000 0000000 #ifndef _POLICY_H_ #define _POLICY_H_ /* Private definitions used internally by libselinux. */ /* xattr name for SELinux attributes. */ #define XATTR_NAME_SELINUX "security.selinux" /* Initial length guess for getting contexts. */ #define INITCONTEXTLEN 255 /* selinuxfs magic number */ #define SELINUX_MAGIC 0xf97cff8c /* Preferred selinuxfs mount point directory paths. */ #define SELINUXMNT "/sys/fs/selinux" #define OLDSELINUXMNT "/selinux" /* selinuxfs filesystem type string. */ #define SELINUXFS "selinuxfs" /* selinuxfs mount point determined at runtime */ extern char *selinux_mnt; /* First version of policy supported in mainline Linux. */ #define DEFAULT_POLICY_VERSION 15 #endif src/policyvers.c0100644 0000000 0000000 00000001415 12763246076 012755 0ustar000000000 0000000 #include #include #include #include #include #include #include "selinux_internal.h" #include #include "policy.h" #include "dso.h" #include int security_policyvers(void) { int fd, ret; char path[PATH_MAX]; char buf[20]; unsigned vers = DEFAULT_POLICY_VERSION; if (!selinux_mnt) { errno = ENOENT; return -1; } snprintf(path, sizeof path, "%s/policyvers", selinux_mnt); fd = open(path, O_RDONLY); if (fd < 0) { if (errno == ENOENT) return vers; else return -1; } memset(buf, 0, sizeof buf); ret = read(fd, buf, sizeof buf - 1); close(fd); if (ret < 0) return -1; if (sscanf(buf, "%u", &vers) != 1) return -1; return vers; } hidden_def(security_policyvers) src/procattr.c0100644 0000000 0000000 00000006541 12763246076 012421 0ustar000000000 0000000 #include #include #include #include #include #include #include #include "selinux_internal.h" #include "policy.h" #ifdef HOST static pid_t gettid(void) { return syscall(__NR_gettid); } #endif static int openattr(pid_t pid, const char *attr, int flags) { int fd, rc; char *path; pid_t tid; if (pid > 0) { rc = asprintf(&path, "/proc/%d/attr/%s", pid, attr); } else if (pid == 0) { rc = asprintf(&path, "/proc/thread-self/attr/%s", attr); if (rc < 0) return -1; fd = open(path, flags | O_CLOEXEC); if (fd >= 0 || errno != ENOENT) goto out; free(path); tid = gettid(); rc = asprintf(&path, "/proc/self/task/%d/attr/%s", tid, attr); } else { errno = EINVAL; return -1; } if (rc < 0) return -1; fd = open(path, flags | O_CLOEXEC); out: free(path); return fd; } static int getprocattrcon(char ** context, pid_t pid, const char *attr) { char *buf; size_t size; int fd; ssize_t ret; int errno_hold; fd = openattr(pid, attr, O_RDONLY); if (fd < 0) return -1; size = selinux_page_size; buf = malloc(size); if (!buf) { ret = -1; goto out; } memset(buf, 0, size); do { ret = read(fd, buf, size - 1); } while (ret < 0 && errno == EINTR); if (ret < 0) goto out2; if (ret == 0) { *context = NULL; goto out2; } *context = strdup(buf); if (!(*context)) { ret = -1; goto out2; } ret = 0; out2: free(buf); out: errno_hold = errno; close(fd); errno = errno_hold; return ret; } static int setprocattrcon(const char * context, pid_t pid, const char *attr) { int fd; ssize_t ret; int errno_hold; fd = openattr(pid, attr, O_RDWR); if (fd < 0) return -1; if (context) do { ret = write(fd, context, strlen(context) + 1); } while (ret < 0 && errno == EINTR); else do { ret = write(fd, NULL, 0); /* clear */ } while (ret < 0 && errno == EINTR); errno_hold = errno; close(fd); errno = errno_hold; if (ret < 0) return -1; else return 0; } #define getselfattr_def(fn, attr) \ int get##fn(char **c) \ { \ return getprocattrcon(c, 0, #attr); \ } #define setselfattr_def(fn, attr) \ int set##fn(const char * c) \ { \ return setprocattrcon(c, 0, #attr); \ } #define all_selfattr_def(fn, attr) \ getselfattr_def(fn, attr) \ setselfattr_def(fn, attr) #define getpidattr_def(fn, attr) \ int get##fn(pid_t pid, char **c) \ { \ if (pid <= 0) { \ errno = EINVAL; \ return -1; \ } else { \ return getprocattrcon(c, pid, #attr); \ } \ } all_selfattr_def(con, current) getpidattr_def(pidcon, current) getselfattr_def(prevcon, prev) all_selfattr_def(execcon, exec) all_selfattr_def(fscreatecon, fscreate) all_selfattr_def(sockcreatecon, sockcreate) all_selfattr_def(keycreatecon, keycreate) hidden_def(getcon_raw) hidden_def(getcon) hidden_def(getexeccon_raw) hidden_def(getfilecon_raw) hidden_def(getfilecon) hidden_def(getfscreatecon_raw) hidden_def(getkeycreatecon_raw) hidden_def(getpeercon_raw) hidden_def(getpidcon_raw) hidden_def(getprevcon_raw) hidden_def(getprevcon) hidden_def(getsockcreatecon_raw) hidden_def(setcon_raw) hidden_def(setexeccon_raw) hidden_def(setexeccon) hidden_def(setfilecon_raw) hidden_def(setfscreatecon_raw) hidden_def(setkeycreatecon_raw) hidden_def(setsockcreatecon_raw) src/selinux_internal.h0100644 0000000 0000000 00000011004 12763246076 014141 0ustar000000000 0000000 #include #include #include "dso.h" hidden_proto(selinux_mkload_policy) hidden_proto(set_selinuxmnt) hidden_proto(security_disable) hidden_proto(security_policyvers) hidden_proto(security_load_policy) hidden_proto(security_get_boolean_active) hidden_proto(security_get_boolean_names) hidden_proto(security_set_boolean) hidden_proto(security_commit_booleans) hidden_proto(security_check_context) hidden_proto(security_check_context_raw) hidden_proto(security_canonicalize_context) hidden_proto(security_canonicalize_context_raw) hidden_proto(security_compute_av) hidden_proto(security_compute_av_raw) hidden_proto(security_compute_av_flags) hidden_proto(security_compute_av_flags_raw) hidden_proto(security_compute_user) hidden_proto(security_compute_user_raw) hidden_proto(security_compute_create) hidden_proto(security_compute_create_raw) hidden_proto(security_compute_member_raw) hidden_proto(security_compute_relabel_raw) hidden_proto(is_selinux_enabled) hidden_proto(is_selinux_mls_enabled) hidden_proto(freecon) hidden_proto(freeconary) hidden_proto(getprevcon) hidden_proto(getprevcon_raw) hidden_proto(getcon) hidden_proto(getcon_raw) hidden_proto(setcon_raw) hidden_proto(getpeercon_raw) hidden_proto(getpidcon_raw) hidden_proto(getexeccon_raw) hidden_proto(getfilecon) hidden_proto(getfilecon_raw) hidden_proto(lgetfilecon_raw) hidden_proto(fgetfilecon_raw) hidden_proto(setfilecon_raw) hidden_proto(lsetfilecon_raw) hidden_proto(fsetfilecon_raw) hidden_proto(setexeccon) hidden_proto(setexeccon_raw) hidden_proto(getfscreatecon_raw) hidden_proto(getkeycreatecon_raw) hidden_proto(getsockcreatecon_raw) hidden_proto(setfscreatecon_raw) hidden_proto(setkeycreatecon_raw) hidden_proto(setsockcreatecon_raw) hidden_proto(security_getenforce) hidden_proto(security_setenforce) hidden_proto(security_deny_unknown) hidden_proto(selinux_binary_policy_path) hidden_proto(selinux_default_context_path) hidden_proto(selinux_securetty_types_path) hidden_proto(selinux_failsafe_context_path) hidden_proto(selinux_removable_context_path) hidden_proto(selinux_virtual_domain_context_path) hidden_proto(selinux_virtual_image_context_path) hidden_proto(selinux_file_context_path) hidden_proto(selinux_file_context_homedir_path) hidden_proto(selinux_file_context_local_path) hidden_proto(selinux_file_context_subs_path) hidden_proto(selinux_netfilter_context_path) hidden_proto(selinux_homedir_context_path) hidden_proto(selinux_user_contexts_path) hidden_proto(selinux_booleans_path) hidden_proto(selinux_customizable_types_path) hidden_proto(selinux_media_context_path) hidden_proto(selinux_x_context_path) hidden_proto(selinux_sepgsql_context_path) hidden_proto(selinux_path) hidden_proto(selinux_check_passwd_access) hidden_proto(selinux_check_securetty_context) hidden_proto(matchpathcon_init_prefix) hidden_proto(selinux_users_path) hidden_proto(selinux_usersconf_path); hidden_proto(selinux_translations_path); hidden_proto(selinux_colors_path); hidden_proto(selinux_getenforcemode); hidden_proto(selinux_getpolicytype); hidden_proto(selinux_raw_to_trans_context); hidden_proto(selinux_trans_to_raw_context); hidden_proto(selinux_raw_context_to_color); hidden_proto(security_get_initial_context); hidden_proto(security_get_initial_context_raw); hidden_proto(selinux_reset_config); extern int selinux_page_size hidden; /* Make pthread_once optional */ #pragma weak pthread_once #pragma weak pthread_key_create #pragma weak pthread_key_delete #pragma weak pthread_setspecific /* Call handler iff the first call. */ #define __selinux_once(ONCE_CONTROL, INIT_FUNCTION) \ do { \ if (pthread_once != NULL) \ pthread_once (&(ONCE_CONTROL), (INIT_FUNCTION)); \ else if ((ONCE_CONTROL) == PTHREAD_ONCE_INIT) { \ INIT_FUNCTION (); \ (ONCE_CONTROL) = 2; \ } \ } while (0) /* Pthread key macros */ #define __selinux_key_create(KEY, DESTRUCTOR) \ do { \ if (pthread_key_create != NULL) \ pthread_key_create(KEY, DESTRUCTOR); \ } while (0) #define __selinux_key_delete(KEY) \ do { \ if (pthread_key_delete != NULL) \ pthread_key_delete(KEY); \ } while (0) #define __selinux_setspecific(KEY, VALUE) \ do { \ if (pthread_setspecific != NULL) \ pthread_setspecific(KEY, VALUE); \ } while (0) src/selinux_netlink.h0100644 0000000 0000000 00000001154 12763246076 013776 0ustar000000000 0000000 /* * Netlink event notifications for SELinux. * * Author: James Morris */ #ifndef _LINUX_SELINUX_NETLINK_H #define _LINUX_SELINUX_NETLINK_H /* Message types. */ #define SELNL_MSG_BASE 0x10 enum { SELNL_MSG_SETENFORCE = SELNL_MSG_BASE, SELNL_MSG_POLICYLOAD, SELNL_MSG_MAX }; /* Multicast groups */ #define SELNL_GRP_NONE 0x00000000 #define SELNL_GRP_AVC 0x00000001 /* AVC notifications */ #define SELNL_GRP_ALL 0xffffffff /* Message structures */ struct selnl_msg_setenforce { int32_t val; }; struct selnl_msg_policyload { uint32_t seqno; }; #endif /* _LINUX_SELINUX_NETLINK_H */ src/sestatus.c0100644 0000000 0000000 00000017110 12763246076 012430 0ustar000000000 0000000 /* * sestatus.c * * APIs to reference SELinux kernel status page (/selinux/status) * * Author: KaiGai Kohei * */ #include #include #include #include #include #include #include #include "avc_internal.h" #include "policy.h" /* * copied from the selinux/include/security.h */ struct selinux_status_t { uint32_t version; /* version number of thie structure */ uint32_t sequence; /* sequence number of seqlock logic */ uint32_t enforcing; /* current setting of enforcing mode */ uint32_t policyload; /* times of policy reloaded */ uint32_t deny_unknown; /* current setting of deny_unknown */ /* version > 0 support above status */ } __attribute((packed)); /* * `selinux_status' * * NULL : not initialized yet * MAP_FAILED : opened, but fallback-mode * Valid Pointer : opened and mapped correctly */ static struct selinux_status_t *selinux_status = NULL; static int selinux_status_fd; static uint32_t last_seqno; static uint32_t fallback_sequence; static int fallback_enforcing; static int fallback_policyload; /* * read_sequence * * A utility routine to reference kernel status page according to * seqlock logic. Since selinux_status->sequence is an odd value during * the kernel status page being updated, we try to synchronize completion * of this updating, but we assume it is rare. * The sequence is almost even number. * * __sync_synchronize is a portable memory barrier for various kind * of architecture that is supported by GCC. */ static inline uint32_t read_sequence(struct selinux_status_t *status) { uint32_t seqno = 0; do { /* * No need for sched_yield() in the first trial of * this loop. */ if (seqno & 0x0001) sched_yield(); seqno = status->sequence; __sync_synchronize(); } while (seqno & 0x0001); return seqno; } /* * selinux_status_updated * * It returns whether something has been happened since the last call. * Because `selinux_status->sequence' shall be always incremented on * both of setenforce/policyreload events, so differences from the last * value informs us something has been happened. */ int selinux_status_updated(void) { uint32_t curr_seqno; int result = 0; if (selinux_status == NULL) { errno = EINVAL; return -1; } if (selinux_status == MAP_FAILED) { if (avc_netlink_check_nb() < 0) return -1; curr_seqno = fallback_sequence; } else { curr_seqno = read_sequence(selinux_status); } /* * `curr_seqno' is always even-number, so it does not match with * `last_seqno' being initialized to odd-number in the first call. * We never return 'something was updated' in the first call, * because this function focuses on status-updating since the last * invocation. */ if (last_seqno & 0x0001) last_seqno = curr_seqno; if (last_seqno != curr_seqno) { last_seqno = curr_seqno; result = 1; } return result; } /* * selinux_status_getenforce * * It returns the current performing mode of SELinux. * 1 means currently we run in enforcing mode, or 0 means permissive mode. */ int selinux_status_getenforce(void) { uint32_t seqno; uint32_t enforcing; if (selinux_status == NULL) { errno = EINVAL; return -1; } if (selinux_status == MAP_FAILED) { if (avc_netlink_check_nb() < 0) return -1; return fallback_enforcing; } /* sequence must not be changed during references */ do { seqno = read_sequence(selinux_status); enforcing = selinux_status->enforcing; } while (seqno != read_sequence(selinux_status)); return enforcing ? 1 : 0; } /* * selinux_status_policyload * * It returns times of policy reloaded on the running system. * Note that it is not a reliable value on fallback-mode until it receives * the first event message via netlink socket, so, a correct usage of this * value is to compare it with the previous value to detect policy reloaded * event. */ int selinux_status_policyload(void) { uint32_t seqno; uint32_t policyload; if (selinux_status == NULL) { errno = EINVAL; return -1; } if (selinux_status == MAP_FAILED) { if (avc_netlink_check_nb() < 0) return -1; return fallback_policyload; } /* sequence must not be changed during references */ do { seqno = read_sequence(selinux_status); policyload = selinux_status->policyload; } while (seqno != read_sequence(selinux_status)); return policyload; } /* * selinux_status_deny_unknown * * It returns a guideline to handle undefined object classes or permissions. * 0 means SELinux treats policy queries on undefined stuff being allowed, * however, 1 means such queries are denied. */ int selinux_status_deny_unknown(void) { uint32_t seqno; uint32_t deny_unknown; if (selinux_status == NULL) { errno = EINVAL; return -1; } if (selinux_status == MAP_FAILED) return security_deny_unknown(); /* sequence must not be changed during references */ do { seqno = read_sequence(selinux_status); deny_unknown = selinux_status->deny_unknown; } while (seqno != read_sequence(selinux_status)); return deny_unknown ? 1 : 0; } /* * callback routines for fallback case using netlink socket */ static int fallback_cb_setenforce(int enforcing) { fallback_sequence += 2; fallback_enforcing = enforcing; return 0; } static int fallback_cb_policyload(int policyload) { fallback_sequence += 2; fallback_policyload = policyload; return 0; } /* * selinux_status_open * * It tries to open and mmap kernel status page (/selinux/status). * Since Linux 2.6.37 or later supports this feature, we may run * fallback routine using a netlink socket on older kernels, if * the supplied `fallback' is not zero. * It returns 0 on success, or -1 on error. */ int selinux_status_open(int fallback) { int fd; char path[PATH_MAX]; long pagesize; if (!selinux_mnt) { errno = ENOENT; return -1; } pagesize = sysconf(_SC_PAGESIZE); if (pagesize < 0) return -1; snprintf(path, sizeof(path), "%s/status", selinux_mnt); fd = open(path, O_RDONLY | O_CLOEXEC); if (fd < 0) goto error; selinux_status = mmap(NULL, pagesize, PROT_READ, MAP_SHARED, fd, 0); if (selinux_status == MAP_FAILED) { close(fd); goto error; } selinux_status_fd = fd; last_seqno = (uint32_t)(-1); return 0; error: /* * If caller wants fallback routine, we try to provide * an equivalent functionality using existing netlink * socket, although it needs system call invocation to * receive event notification. */ if (fallback && avc_netlink_open(0) == 0) { union selinux_callback cb; /* register my callbacks */ cb.func_setenforce = fallback_cb_setenforce; selinux_set_callback(SELINUX_CB_SETENFORCE, cb); cb.func_policyload = fallback_cb_policyload; selinux_set_callback(SELINUX_CB_POLICYLOAD, cb); /* mark as fallback mode */ selinux_status = MAP_FAILED; selinux_status_fd = avc_netlink_acquire_fd(); last_seqno = (uint32_t)(-1); fallback_sequence = 0; fallback_enforcing = security_getenforce(); fallback_policyload = 0; return 1; } selinux_status = NULL; return -1; } /* * selinux_status_close * * It unmap and close the kernel status page, or close netlink socket * if fallback mode. */ void selinux_status_close(void) { long pagesize; /* not opened */ if (selinux_status == NULL) return; /* fallback-mode */ if (selinux_status == MAP_FAILED) { avc_netlink_release_fd(); avc_netlink_close(); selinux_status = NULL; return; } pagesize = sysconf(_SC_PAGESIZE); /* not much we can do other than leak memory */ if (pagesize > 0) munmap(selinux_status, pagesize); selinux_status = NULL; close(selinux_status_fd); selinux_status_fd = -1; last_seqno = (uint32_t)(-1); } src/setenforce.c0100644 0000000 0000000 00000001165 12763246076 012715 0ustar000000000 0000000 #include #include #include #include #include #include #include "selinux_internal.h" #include "policy.h" #include #include int security_setenforce(int value) { int fd, ret; char path[PATH_MAX]; char buf[20]; if (!selinux_mnt) { errno = ENOENT; return -1; } snprintf(path, sizeof path, "%s/enforce", selinux_mnt); fd = open(path, O_RDWR); if (fd < 0) return -1; snprintf(buf, sizeof buf, "%d", value); ret = write(fd, buf, strlen(buf)); close(fd); if (ret < 0) return -1; return 0; } hidden_def(security_setenforce) src/setfilecon.c0100644 0000000 0000000 00000000467 12763246076 012717 0ustar000000000 0000000 #include #include #include #include #include #include #include "selinux_internal.h" #include "policy.h" int setfilecon(const char *path, const char *context) { return setxattr(path, XATTR_NAME_SELINUX, context, strlen(context) + 1, 0); } src/stringrep.c0100644 0000000 0000000 00000012204 12763246076 012571 0ustar000000000 0000000 /* * String representation support for classes and permissions. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "selinux_internal.h" #include "policy.h" #include "mapping.h" #define MAXVECTORS 8*sizeof(access_vector_t) struct discover_class_node { char *name; security_class_t value; char **perms; struct discover_class_node *next; }; static struct discover_class_node *discover_class_cache = NULL; static struct discover_class_node * get_class_cache_entry_name(const char *s) { struct discover_class_node *node = discover_class_cache; for (; node != NULL && strcmp(s,node->name) != 0; node = node->next); return node; } static struct discover_class_node * get_class_cache_entry_value(security_class_t c) { struct discover_class_node *node = discover_class_cache; for (; node != NULL && c != node->value; node = node->next); return node; } static struct discover_class_node * discover_class(const char *s) { int fd, ret; char path[PATH_MAX]; char buf[20]; DIR *dir; struct dirent *dentry; size_t i; struct discover_class_node *node; if (!selinux_mnt) { errno = ENOENT; return NULL; } /* allocate a node */ node = malloc(sizeof(struct discover_class_node)); if (node == NULL) return NULL; /* allocate array for perms */ node->perms = calloc(MAXVECTORS,sizeof(char*)); if (node->perms == NULL) goto err1; /* load up the name */ node->name = strdup(s); if (node->name == NULL) goto err2; /* load up class index */ snprintf(path, sizeof path, "%s/class/%s/index", selinux_mnt,s); fd = open(path, O_RDONLY); if (fd < 0) goto err3; memset(buf, 0, sizeof(buf)); ret = read(fd, buf, sizeof(buf) - 1); close(fd); if (ret < 0) goto err3; if (sscanf(buf, "%hu", &node->value) != 1) goto err3; /* load up permission indicies */ snprintf(path, sizeof path, "%s/class/%s/perms",selinux_mnt,s); dir = opendir(path); if (dir == NULL) goto err3; dentry = readdir(dir); while (dentry != NULL) { unsigned int value; struct stat m; snprintf(path, sizeof path, "%s/class/%s/perms/%s", selinux_mnt,s,dentry->d_name); fd = open(path, O_RDONLY | O_CLOEXEC); if (fd < 0) goto err4; if (fstat(fd, &m) < 0) { close(fd); goto err4; } if (m.st_mode & S_IFDIR) { close(fd); dentry = readdir(dir); continue; } memset(buf, 0, sizeof(buf)); ret = read(fd, buf, sizeof(buf) - 1); close(fd); if (ret < 0) goto err4; if (sscanf(buf, "%u", &value) != 1) goto err4; if (value == 0 || value > MAXVECTORS) goto err4; node->perms[value-1] = strdup(dentry->d_name); if (node->perms[value-1] == NULL) goto err4; dentry = readdir(dir); } closedir(dir); node->next = discover_class_cache; discover_class_cache = node; return node; err4: closedir(dir); for (i=0; iperms[i]); err3: free(node->name); err2: free(node->perms); err1: free(node); return NULL; } security_class_t string_to_security_class(const char *s) { struct discover_class_node *node; node = get_class_cache_entry_name(s); if (node == NULL) { node = discover_class(s); if (node == NULL) { errno = EINVAL; return 0; } } return map_class(node->value); } access_vector_t string_to_av_perm(security_class_t tclass, const char *s) { struct discover_class_node *node; security_class_t kclass = unmap_class(tclass); node = get_class_cache_entry_value(kclass); if (node != NULL) { size_t i; for (i=0; iperms[i] != NULL; i++) if (strcmp(node->perms[i],s) == 0) return map_perm(tclass, 1<name; return NULL; } const char *security_av_perm_to_string(security_class_t tclass, access_vector_t av) { struct discover_class_node *node; size_t i; av = unmap_perm(tclass, av); tclass = unmap_class(tclass); node = get_class_cache_entry_value(tclass); if (av && node) for (i = 0; iperms[i]; return NULL; } int security_av_string(security_class_t tclass, access_vector_t av, char **res) { unsigned int i = 0; size_t len = 5; access_vector_t tmp = av; int rc = 0; const char *str; char *ptr; /* first pass computes the required length */ while (tmp) { if (tmp & 1) { str = security_av_perm_to_string(tclass, av & (1<>= 1; i++; } *res = malloc(len); if (!*res) { rc = -1; goto out; } /* second pass constructs the string */ i = 0; tmp = av; ptr = *res; if (!av) { sprintf(ptr, "null"); goto out; } ptr += sprintf(ptr, "{ "); while (tmp) { if (tmp & 1) ptr += sprintf(ptr, "%s ", security_av_perm_to_string( tclass, av & (1<>= 1; i++; } sprintf(ptr, "}"); out: return rc; } utils/0040755 0000000 0000000 00000000000 12763246076 010765 5ustar000000000 0000000 utils/sefcontext_compile.c0100644 0000000 0000000 00000022105 12763246076 015020 0ustar000000000 0000000 #include #include #include #include #include #include #include #include #include #include #include #include #include "../src/label_file.h" static int validate_context(char __attribute__ ((unused)) **ctx) { return 0; } static int process_file(struct selabel_handle *rec, const char *filename) { unsigned int line_num; int rc; char *line_buf = NULL; size_t line_len = 0; FILE *context_file; const char *prefix = NULL; context_file = fopen(filename, "r"); if (!context_file) { fprintf(stderr, "Error opening %s: %s\n", filename, strerror(errno)); return -1; } line_num = 0; rc = 0; while (getline(&line_buf, &line_len, context_file) > 0) { rc = process_line(rec, filename, prefix, line_buf, ++line_num); if (rc) goto out; } out: free(line_buf); fclose(context_file); return rc; } /* * File Format * * u32 - magic number * u32 - version * u32 - length of pcre version EXCLUDING nul * char - pcre version string EXCLUDING nul * u32 - number of stems * ** Stems * u32 - length of stem EXCLUDING nul * char - stem char array INCLUDING nul * u32 - number of regexs * ** Regexes * u32 - length of upcoming context INCLUDING nul * char - char array of the raw context * u32 - length of the upcoming regex_str * char - char array of the original regex string including the stem. * u32 - mode bits for >= SELINUX_COMPILED_FCONTEXT_MODE * mode_t for <= SELINUX_COMPILED_FCONTEXT_PCRE_VERS * s32 - stemid associated with the regex * u32 - spec has meta characters * u32 - The specs prefix_len if >= SELINUX_COMPILED_FCONTEXT_PREFIX_LEN * u32 - data length of the pcre regex * char - a bufer holding the raw pcre regex info * u32 - data length of the pcre regex study daya * char - a buffer holding the raw pcre regex study data */ static int write_binary_file(struct saved_data *data, int fd) { struct spec *specs = data->spec_arr; FILE *bin_file; size_t len; uint32_t magic = SELINUX_MAGIC_COMPILED_FCONTEXT; uint32_t section_len; uint32_t i; int rc; bin_file = fdopen(fd, "w"); if (!bin_file) { perror("fopen output_file"); exit(EXIT_FAILURE); } /* write some magic number */ len = fwrite(&magic, sizeof(uint32_t), 1, bin_file); if (len != 1) goto err; /* write the version */ section_len = SELINUX_COMPILED_FCONTEXT_MAX_VERS; len = fwrite(§ion_len, sizeof(uint32_t), 1, bin_file); if (len != 1) goto err; /* write the pcre version */ section_len = strlen(pcre_version()); len = fwrite(§ion_len, sizeof(uint32_t), 1, bin_file); if (len != 1) goto err; len = fwrite(pcre_version(), sizeof(char), section_len, bin_file); if (len != section_len) goto err; /* write the number of stems coming */ section_len = data->num_stems; len = fwrite(§ion_len, sizeof(uint32_t), 1, bin_file); if (len != 1) goto err; for (i = 0; i < section_len; i++) { char *stem = data->stem_arr[i].buf; uint32_t stem_len = data->stem_arr[i].len; /* write the strlen (aka no nul) */ len = fwrite(&stem_len, sizeof(uint32_t), 1, bin_file); if (len != 1) goto err; /* include the nul in the file */ stem_len += 1; len = fwrite(stem, sizeof(char), stem_len, bin_file); if (len != stem_len) goto err; } /* write the number of regexes coming */ section_len = data->nspec; len = fwrite(§ion_len, sizeof(uint32_t), 1, bin_file); if (len != 1) goto err; for (i = 0; i < section_len; i++) { char *context = specs[i].lr.ctx_raw; char *regex_str = specs[i].regex_str; mode_t mode = specs[i].mode; size_t prefix_len = specs[i].prefix_len; int32_t stem_id = specs[i].stem_id; pcre *re = specs[i].regex; pcre_extra *sd = get_pcre_extra(&specs[i]); uint32_t to_write; size_t size; /* length of the context string (including nul) */ to_write = strlen(context) + 1; len = fwrite(&to_write, sizeof(uint32_t), 1, bin_file); if (len != 1) goto err; /* original context strin (including nul) */ len = fwrite(context, sizeof(char), to_write, bin_file); if (len != to_write) goto err; /* length of the original regex string (including nul) */ to_write = strlen(regex_str) + 1; len = fwrite(&to_write, sizeof(uint32_t), 1, bin_file); if (len != 1) goto err; /* original regex string */ len = fwrite(regex_str, sizeof(char), to_write, bin_file); if (len != to_write) goto err; /* binary F_MODE bits */ to_write = mode; len = fwrite(&to_write, sizeof(uint32_t), 1, bin_file); if (len != 1) goto err; /* stem for this regex (could be -1) */ len = fwrite(&stem_id, sizeof(stem_id), 1, bin_file); if (len != 1) goto err; /* does this spec have a metaChar? */ to_write = specs[i].hasMetaChars; len = fwrite(&to_write, sizeof(to_write), 1, bin_file); if (len != 1) goto err; /* For SELINUX_COMPILED_FCONTEXT_PREFIX_LEN */ to_write = prefix_len; len = fwrite(&to_write, sizeof(to_write), 1, bin_file); if (len != 1) goto err; /* determine the size of the pcre data in bytes */ rc = pcre_fullinfo(re, NULL, PCRE_INFO_SIZE, &size); if (rc < 0) goto err; /* write the number of bytes in the pcre data */ to_write = size; len = fwrite(&to_write, sizeof(uint32_t), 1, bin_file); if (len != 1) goto err; /* write the actual pcre data as a char array */ len = fwrite(re, 1, to_write, bin_file); if (len != to_write) goto err; /* determine the size of the pcre study info */ rc = pcre_fullinfo(re, sd, PCRE_INFO_STUDYSIZE, &size); if (rc < 0) goto err; /* write the number of bytes in the pcre study data */ to_write = size; len = fwrite(&to_write, sizeof(uint32_t), 1, bin_file); if (len != 1) goto err; /* write the actual pcre study data as a char array */ len = fwrite(sd->study_data, 1, to_write, bin_file); if (len != to_write) goto err; } rc = 0; out: fclose(bin_file); return rc; err: rc = -1; goto out; } static void free_specs(struct saved_data *data) { struct spec *specs = data->spec_arr; unsigned int num_entries = data->nspec; unsigned int i; for (i = 0; i < num_entries; i++) { free(specs[i].lr.ctx_raw); free(specs[i].lr.ctx_trans); free(specs[i].regex_str); free(specs[i].type_str); pcre_free(specs[i].regex); pcre_free_study(specs[i].sd); } free(specs); num_entries = data->num_stems; for (i = 0; i < num_entries; i++) free(data->stem_arr[i].buf); free(data->stem_arr); memset(data, 0, sizeof(*data)); } static void usage(const char *progname) { fprintf(stderr, "usage: %s [-o out_file] fc_file\n" "Where:\n\t" "-o Optional file name of the PCRE formatted binary\n\t" " file to be output. If not specified the default\n\t" " will be fc_file with the .bin suffix appended.\n\t" "fc_file The text based file contexts file to be processed.\n", progname); exit(EXIT_FAILURE); } int main(int argc, char *argv[]) { const char *path = NULL; const char *out_file = NULL; char stack_path[PATH_MAX + 1]; char *tmp = NULL; int fd, rc, opt; struct stat buf; struct selabel_handle *rec = NULL; struct saved_data *data = NULL; if (argc < 2) usage(argv[0]); while ((opt = getopt(argc, argv, "o:")) > 0) { switch (opt) { case 'o': out_file = optarg; break; default: usage(argv[0]); } } if (optind >= argc) usage(argv[0]); path = argv[optind]; if (stat(path, &buf) < 0) { fprintf(stderr, "Can not stat: %s: %m\n", path); exit(EXIT_FAILURE); } /* Generate dummy handle for process_line() function */ rec = (struct selabel_handle *)calloc(1, sizeof(*rec)); if (!rec) { fprintf(stderr, "Failed to calloc handle\n"); exit(EXIT_FAILURE); } rec->backend = SELABEL_CTX_FILE; /* Need to set validation on to get the bin file generated by the * process_line function, however as the bin file being generated * may not be related to the currently loaded policy (that it * would be validated against), then set callback to ignore any * validation. */ rec->validating = 1; selinux_set_callback(SELINUX_CB_VALIDATE, (union selinux_callback)&validate_context); data = (struct saved_data *)calloc(1, sizeof(*data)); if (!data) { fprintf(stderr, "Failed to calloc saved_data\n"); free(rec); exit(EXIT_FAILURE); } rec->data = data; rc = process_file(rec, path); if (rc < 0) goto err; rc = sort_specs(data); if (rc) goto err; if (out_file) rc = snprintf(stack_path, sizeof(stack_path), "%s", out_file); else rc = snprintf(stack_path, sizeof(stack_path), "%s.bin", path); if (rc < 0 || rc >= (int)sizeof(stack_path)) goto err; tmp = malloc(strlen(stack_path) + 7); if (!tmp) goto err; rc = sprintf(tmp, "%sXXXXXX", stack_path); if (rc < 0) goto err; fd = mkstemp(tmp); if (fd < 0) goto err; rc = fchmod(fd, buf.st_mode); if (rc < 0) { perror("fchmod failed to set permission on compiled regexs"); goto err_unlink; } rc = write_binary_file(data, fd); if (rc < 0) goto err_unlink; rc = rename(tmp, stack_path); if (rc < 0) goto err_unlink; rc = 0; out: free_specs(data); free(rec); free(data); free(tmp); return rc; err_unlink: unlink(tmp); err: rc = -1; goto out; }