Project import
diff --git a/libselinux/Android.bp b/libselinux/Android.bp new file mode 100644 index 0000000..9c4b1d4 --- /dev/null +++ b/libselinux/Android.bp
@@ -0,0 +1,119 @@ +common_LIBRARIES = ["libpcre2"] +common_CFLAGS = [ + "-DUSE_PCRE2", + + // Persistently stored patterns (pcre2) are architecture dependent. + // In particular paterns built on amd64 can not run on devices with armv7 + // (32bit). Therefore, this feature stays off for now. + "-DNO_PERSISTENTLY_STORED_PATTERNS", +] + +// uncomment to build libselinux and related artifacts against PCRE +// common_LIBRARIES = ["libpcre"] +// common_CFLAGS = [] + +cc_defaults { + name: "libselinux_flags", + + cflags: common_CFLAGS, + + target: { + host: { + cflags: ["-DHOST"], + }, + darwin: { + cflags: ["-DDARWIN"], + }, + }, +} + +cc_library { + name: "libselinux", + defaults: ["libselinux_flags"], + host_supported: true, + + srcs: [ + "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/regex.c", + "src/label_support.c", + ], + + target: { + android: { + srcs: [ + "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", + + "src/android.c", + ], + + shared_libs: [ + "libcrypto", + "liblog", + ], + static: { + whole_static_libs: ["libpackagelistparser"], + }, + shared: { + shared_libs: ["libpackagelistparser"], + }, + + // 1003 corresponds to auditd, from system/core/logd/event.logtags + cflags: ["-DAUDITD_LOG_TAG=1003"], + // mapping.c has redundant check of array p_in->perms. + clang_cflags: ["-Wno-pointer-bool-conversion"], + }, + }, + + static: { + whole_static_libs: common_LIBRARIES, + }, + shared: { + shared_libs: common_LIBRARIES, + }, + + local_include_dirs: ["include"], + export_include_dirs: ["include"], +} + +//################################ +cc_binary_host { + name: "sefcontext_compile", + defaults: ["libselinux_flags"], + srcs: ["utils/sefcontext_compile.c"], + + static_libs: ["libselinux"], + whole_static_libs: common_LIBRARIES, +}
diff --git a/libselinux/MODULE_LICENSE_PUBLIC_DOMAIN b/libselinux/MODULE_LICENSE_PUBLIC_DOMAIN new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/libselinux/MODULE_LICENSE_PUBLIC_DOMAIN
diff --git a/libselinux/NOTICE b/libselinux/NOTICE new file mode 100644 index 0000000..d386268 --- /dev/null +++ b/libselinux/NOTICE
@@ -0,0 +1,21 @@ +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.
diff --git a/libselinux/README.android b/libselinux/README.android new file mode 100644 index 0000000..e01f889 --- /dev/null +++ b/libselinux/README.android
@@ -0,0 +1,62 @@ +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)
diff --git a/libselinux/include/selinux/android.h b/libselinux/include/selinux/android.h new file mode 100644 index 0000000..21a41a0 --- /dev/null +++ b/libselinux/include/selinux/android.h
@@ -0,0 +1,56 @@ +#ifndef _SELINUX_ANDROID_H_ +#define _SELINUX_ANDROID_H_ + +#include <stdbool.h> +#include <sys/types.h> +#include <unistd.h> + +#include <selinux/label.h> + +#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_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
diff --git a/libselinux/include/selinux/avc.h b/libselinux/include/selinux/avc.h new file mode 100644 index 0000000..8a1a6df --- /dev/null +++ b/libselinux/include/selinux/avc.h
@@ -0,0 +1,507 @@ +/* + * Access vector cache interface for object managers. + * + * Author : Eamon Walsh <ewalsh@epoch.ncsc.mil> + */ +#ifndef _SELINUX_AVC_H_ +#define _SELINUX_AVC_H_ + +#include <stdint.h> +#include <errno.h> +#include <stdlib.h> +#include <selinux/selinux.h> + +#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_ */
diff --git a/libselinux/include/selinux/context.h b/libselinux/include/selinux/context.h new file mode 100644 index 0000000..949fb1e --- /dev/null +++ b/libselinux/include/selinux/context.h
@@ -0,0 +1,50 @@ +#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
diff --git a/libselinux/include/selinux/label.h b/libselinux/include/selinux/label.h new file mode 100644 index 0000000..512c71f --- /dev/null +++ b/libselinux/include/selinux/label.h
@@ -0,0 +1,165 @@ +/* + * Labeling interface for userspace object managers and others. + * + * Author : Eamon Walsh <ewalsh@tycho.nsa.gov> + */ +#ifndef _SELABEL_H_ +#define _SELABEL_H_ + +#include <stdbool.h> +#include <sys/types.h> +#include <selinux/selinux.h> + +#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_ */
diff --git a/libselinux/include/selinux/selinux.h b/libselinux/include/selinux/selinux.h new file mode 100644 index 0000000..8827da8 --- /dev/null +++ b/libselinux/include/selinux/selinux.h
@@ -0,0 +1,291 @@ +#ifndef _SELINUX_H_ +#define _SELINUX_H_ + +#include <sys/types.h> +#include <stdarg.h> + +#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
diff --git a/libselinux/src/android.c b/libselinux/src/android.c new file mode 100644 index 0000000..b748ca5 --- /dev/null +++ b/libselinux/src/android.c
@@ -0,0 +1,1516 @@ +#include <sys/types.h> +#include <unistd.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <ctype.h> +#include <errno.h> +#include <pwd.h> +#include <grp.h> +#include <sys/mman.h> +#include <sys/mount.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/xattr.h> +#include <fcntl.h> +#include <fts.h> +#include <selinux/selinux.h> +#include <selinux/context.h> +#include <selinux/android.h> +#include <selinux/label.h> +#include <selinux/avc.h> +#include <openssl/sha.h> +#include <private/android_filesystem_config.h> +#include <log/log.h> +#include "policy.h" +#include "callbacks.h" +#include "selinux_internal.h" +#include "label_internal.h" +#include <fnmatch.h> +#include <limits.h> +#include <sys/vfs.h> +#include <linux/magic.h> +#include <libgen.h> +#include <packagelistparser/packagelistparser.h> + +#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_ +#include <sys/_system_properties.h> + +/* + * 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"; + +static const struct selinux_opt seopts = + { SELABEL_OPT_PATH, "/file_contexts.bin" }; + +static const char *const sepolicy_file = "/sepolicy"; + +static const struct selinux_opt seopts_prop = + { SELABEL_OPT_PATH, "/property_contexts" }; + +static const struct selinux_opt seopts_service = + { SELABEL_OPT_PATH, "/service_contexts" }; + +enum levelFrom { + LEVELFROM_NONE, + LEVELFROM_APP, + LEVELFROM_USER, + LEVELFROM_ALL +}; + +#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 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 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; + + fp = fopen(seapp_contexts_file, "re"); + 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, "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, 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 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->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, 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" +static bool is_app_privileged(const char *seinfo) +{ + return strstr(seinfo, PRIVILEGED_APP_STR) != NULL; +} + +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) +{ + struct passwd *pwd; + 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; + char parsedseinfo[BUFSIZ]; + + __selinux_once(once, seapp_context_init); + + if (seinfo) { + if (seinfo_parse(parsedseinfo, seinfo, BUFSIZ)) + goto err; + isPrivApp = is_app_privileged(seinfo); + seinfo = parsedseinfo; + } + + userid = uid / AID_USER; + isOwner = (userid == 0); + appid = uid % AID_USER; + if (appid < AID_APP) { + /* + * This code is Android specific, bionic guarantees that + * calls to non-reentrant getpwuid() are thread safe. + */ +#ifndef __BIONIC__ +#warning "This code assumes that getpwuid is thread safe, only true with Bionic!" +#endif + pwd = getpwuid(appid); + if (!pwd) + goto err; + + username = pwd->pw_name; + + } 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->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_file_contexts_hash(uint8_t c_digest[]) +{ + int fd; + struct stat sb; + void *map; + + fd = open(seopts.value, O_CLOEXEC | O_RDONLY | O_NOFOLLOW); + if (fd < 0) { + selinux_log(SELINUX_ERROR, "SELinux: Could not open %s: %s\n", + seopts.value, strerror(errno)); + return false; + } + if (fstat(fd, &sb) < 0) { + selinux_log(SELINUX_ERROR, "SELinux: Could not stat %s: %s\n", + seopts.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", + seopts.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 } + }; + + fc_opts[0].value = seopts.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_file_contexts_hash(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; + + sehandle = selabel_open(SELABEL_CTX_ANDROID_PROP, + &seopts_prop, 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.value); + + return sehandle; +} + +struct selabel_handle* selinux_android_service_context_handle(void) +{ + struct selabel_handle* sehandle; + + sehandle = selabel_open(SELABEL_CTX_ANDROID_PROP, + &seopts_service, 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.value); + + return sehandle; +} + +void selinux_android_set_sehandle(const struct selabel_handle *hndl) +{ + fc_sehandle = (struct selabel_handle *) hndl; +} + +int selinux_android_load_policy(void) +{ + int fd = -1, rc; + struct stat sb; + void *map = NULL; + static int load_successful = 0; + + /* + * Since updating policy at runtime has been abolished + * we just check whether a policy has been loaded before + * and return if this is the case. + * There is no point in reloading policy. + */ + if (load_successful){ + selinux_log(SELINUX_WARNING, "SELinux: Attempted reload of SELinux policy!/n"); + return 0; + } + + set_selinuxmnt(SELINUXMNT); + fd = open(sepolicy_file, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); + 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, 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, 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); + load_successful = 1; + return 0; +} + +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; +}
diff --git a/libselinux/src/avc.c b/libselinux/src/avc.c new file mode 100644 index 0000000..528d897 --- /dev/null +++ b/libselinux/src/avc.c
@@ -0,0 +1,1121 @@ +/* + * Implementation of the userspace access vector cache (AVC). + * + * Author : Eamon Walsh <ewalsh@epoch.ncsc.mil> + * + * Derived from the kernel AVC implementation by + * Stephen Smalley <sds@epoch.ncsc.mil> and + * James Morris <jmorris@redhat.com>. + */ +#include <selinux/avc.h> +#include "selinux_internal.h" +#include <assert.h> +#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); +}
diff --git a/libselinux/src/avc_internal.c b/libselinux/src/avc_internal.c new file mode 100644 index 0000000..c89a886 --- /dev/null +++ b/libselinux/src/avc_internal.c
@@ -0,0 +1,292 @@ +/* + * Callbacks for user-supplied memory allocation, supplemental + * auditing, and locking routines. + * + * Author : Eamon Walsh <ewalsh@epoch.ncsc.mil> + * + * Netlink code derived in part from sample code by + * James Morris <jmorris@redhat.com>. + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <poll.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <linux/types.h> +#include <linux/netlink.h> +#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; +}
diff --git a/libselinux/src/avc_internal.h b/libselinux/src/avc_internal.h new file mode 100644 index 0000000..53610e8 --- /dev/null +++ b/libselinux/src/avc_internal.h
@@ -0,0 +1,182 @@ +/* + * 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 <ewalsh@epoch.ncsc.mil> + */ +#ifndef _SELINUX_AVC_INTERNAL_H_ +#define _SELINUX_AVC_INTERNAL_H_ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <selinux/avc.h> +#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_ */
diff --git a/libselinux/src/avc_sidtab.c b/libselinux/src/avc_sidtab.c new file mode 100644 index 0000000..52f21df --- /dev/null +++ b/libselinux/src/avc_sidtab.c
@@ -0,0 +1,152 @@ +/* + * Implementation of the userspace SID hashtable. + * + * Author : Eamon Walsh, <ewalsh@epoch.ncsc.mil> + */ +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include "selinux_internal.h" +#include <selinux/avc.h> +#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; +}
diff --git a/libselinux/src/avc_sidtab.h b/libselinux/src/avc_sidtab.h new file mode 100644 index 0000000..bce9b87 --- /dev/null +++ b/libselinux/src/avc_sidtab.h
@@ -0,0 +1,36 @@ +/* + * 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 <selinux/selinux.h> +#include <selinux/avc.h> +#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_ */
diff --git a/libselinux/src/booleans.c b/libselinux/src/booleans.c new file mode 100644 index 0000000..17e0ad8 --- /dev/null +++ b/libselinux/src/booleans.c
@@ -0,0 +1,263 @@ +/* + * Author: Karl MacMillan <kmacmillan@tresys.com> + * + * Modified: + * Dan Walsh <dwalsh@redhat.com> - Added security_load_booleans(). + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <assert.h> +#include <stdlib.h> +#include <dirent.h> +#include <string.h> +#include <stdio.h> +#include <unistd.h> +#include <fnmatch.h> +#include <limits.h> +#include <ctype.h> +#include <errno.h> + +#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; +}
diff --git a/libselinux/src/callbacks.c b/libselinux/src/callbacks.c new file mode 100644 index 0000000..c3cf98b --- /dev/null +++ b/libselinux/src/callbacks.c
@@ -0,0 +1,124 @@ +/* + * User-supplied callbacks and default implementations. + * Class and permission mappings. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <errno.h> +#include <selinux/selinux.h> +#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; +}
diff --git a/libselinux/src/callbacks.h b/libselinux/src/callbacks.h new file mode 100644 index 0000000..2a572e0 --- /dev/null +++ b/libselinux/src/callbacks.h
@@ -0,0 +1,30 @@ +/* + * 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <selinux/selinux.h> +#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_ */
diff --git a/libselinux/src/canonicalize_context.c b/libselinux/src/canonicalize_context.c new file mode 100644 index 0000000..b8f874f --- /dev/null +++ b/libselinux/src/canonicalize_context.c
@@ -0,0 +1,62 @@ +#include <unistd.h> +#include <sys/types.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include "selinux_internal.h" +#include "policy.h" +#include <limits.h> + +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; +} +
diff --git a/libselinux/src/checkAccess.c b/libselinux/src/checkAccess.c new file mode 100644 index 0000000..dc11cf7 --- /dev/null +++ b/libselinux/src/checkAccess.c
@@ -0,0 +1,62 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ +#include <unistd.h> +#include <sys/types.h> +#include <stdlib.h> +#include <errno.h> +#include "selinux_internal.h" +#include <selinux/avc.h> +#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); +} +
diff --git a/libselinux/src/check_context.c b/libselinux/src/check_context.c new file mode 100644 index 0000000..7471194 --- /dev/null +++ b/libselinux/src/check_context.c
@@ -0,0 +1,33 @@ +#include <unistd.h> +#include <sys/types.h> +#include <fcntl.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include "selinux_internal.h" +#include "policy.h" +#include <limits.h> + +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; +} +
diff --git a/libselinux/src/compute_av.c b/libselinux/src/compute_av.c new file mode 100644 index 0000000..d6f76f8 --- /dev/null +++ b/libselinux/src/compute_av.c
@@ -0,0 +1,72 @@ +#include <unistd.h> +#include <sys/types.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <limits.h> +#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; +} +
diff --git a/libselinux/src/compute_create.c b/libselinux/src/compute_create.c new file mode 100644 index 0000000..d3b16c9 --- /dev/null +++ b/libselinux/src/compute_create.c
@@ -0,0 +1,61 @@ +#include <unistd.h> +#include <sys/types.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <limits.h> +#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; +}
diff --git a/libselinux/src/context.c b/libselinux/src/context.c new file mode 100644 index 0000000..66abea1 --- /dev/null +++ b/libselinux/src/context.c
@@ -0,0 +1,197 @@ +#include "context_internal.h" +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> + +#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)
diff --git a/libselinux/src/context_internal.h b/libselinux/src/context_internal.h new file mode 100644 index 0000000..3c71e80 --- /dev/null +++ b/libselinux/src/context_internal.h
@@ -0,0 +1,14 @@ +#include <selinux/context.h> +#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)
diff --git a/libselinux/src/deny_unknown.c b/libselinux/src/deny_unknown.c new file mode 100644 index 0000000..c93998a --- /dev/null +++ b/libselinux/src/deny_unknown.c
@@ -0,0 +1,40 @@ +#include <unistd.h> +#include <sys/types.h> +#include <fcntl.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include "selinux_internal.h" +#include "policy.h" +#include <stdio.h> +#include <limits.h> + +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);
diff --git a/libselinux/src/disable.c b/libselinux/src/disable.c new file mode 100644 index 0000000..dac0f5b --- /dev/null +++ b/libselinux/src/disable.c
@@ -0,0 +1,38 @@ +#include <unistd.h> +#include <sys/types.h> +#include <fcntl.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include "selinux_internal.h" +#include "policy.h" +#include <stdio.h> +#include <limits.h> + +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)
diff --git a/libselinux/src/dso.h b/libselinux/src/dso.h new file mode 100644 index 0000000..12c3d11 --- /dev/null +++ b/libselinux/src/dso.h
@@ -0,0 +1,23 @@ +#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
diff --git a/libselinux/src/enabled.c b/libselinux/src/enabled.c new file mode 100644 index 0000000..c60eb19 --- /dev/null +++ b/libselinux/src/enabled.c
@@ -0,0 +1,54 @@ +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include "selinux_internal.h" +#include <stdlib.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#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)
diff --git a/libselinux/src/fgetfilecon.c b/libselinux/src/fgetfilecon.c new file mode 100644 index 0000000..33cdc27 --- /dev/null +++ b/libselinux/src/fgetfilecon.c
@@ -0,0 +1,51 @@ +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <sys/xattr.h> +#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; +} +
diff --git a/libselinux/src/freecon.c b/libselinux/src/freecon.c new file mode 100644 index 0000000..5290dfa --- /dev/null +++ b/libselinux/src/freecon.c
@@ -0,0 +1,11 @@ +#include <unistd.h> +#include "selinux_internal.h" +#include <stdlib.h> +#include <errno.h> + +void freecon(char * con) +{ + free(con); +} + +hidden_def(freecon)
diff --git a/libselinux/src/fsetfilecon.c b/libselinux/src/fsetfilecon.c new file mode 100644 index 0000000..17f8875 --- /dev/null +++ b/libselinux/src/fsetfilecon.c
@@ -0,0 +1,15 @@ +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <sys/xattr.h> +#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); +} +
diff --git a/libselinux/src/get_initial_context.c b/libselinux/src/get_initial_context.c new file mode 100644 index 0000000..64863dd --- /dev/null +++ b/libselinux/src/get_initial_context.c
@@ -0,0 +1,55 @@ +#include <unistd.h> +#include <sys/types.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include "selinux_internal.h" +#include "policy.h" +#include <limits.h> + +#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; +} +
diff --git a/libselinux/src/getenforce.c b/libselinux/src/getenforce.c new file mode 100644 index 0000000..4fb516a --- /dev/null +++ b/libselinux/src/getenforce.c
@@ -0,0 +1,40 @@ +#include <unistd.h> +#include <sys/types.h> +#include <fcntl.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include "selinux_internal.h" +#include "policy.h" +#include <stdio.h> +#include <limits.h> + +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)
diff --git a/libselinux/src/getfilecon.c b/libselinux/src/getfilecon.c new file mode 100644 index 0000000..02037de --- /dev/null +++ b/libselinux/src/getfilecon.c
@@ -0,0 +1,50 @@ +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include "selinux_internal.h" +#include <stdlib.h> +#include <errno.h> +#include <sys/xattr.h> +#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; +}
diff --git a/libselinux/src/getpeercon.c b/libselinux/src/getpeercon.c new file mode 100644 index 0000000..3bd29dc --- /dev/null +++ b/libselinux/src/getpeercon.c
@@ -0,0 +1,45 @@ +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <sys/socket.h> +#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; +} +
diff --git a/libselinux/src/init.c b/libselinux/src/init.c new file mode 100644 index 0000000..65bc01b --- /dev/null +++ b/libselinux/src/init.c
@@ -0,0 +1,123 @@ +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <ctype.h> +#include <stdio.h> +#include <dlfcn.h> + +#ifdef DARWIN +#include <sys/param.h> +#include <sys/mount.h> +#else +#include <sys/vfs.h> +#endif + +#include <stdint.h> +#include <limits.h> + +#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(); +}
diff --git a/libselinux/src/label.c b/libselinux/src/label.c new file mode 100644 index 0000000..fb8c266 --- /dev/null +++ b/libselinux/src/label.c
@@ -0,0 +1,172 @@ +/* + * Generalized labeling frontend for userspace object managers. + * + * Author : Eamon Walsh <ewalsh@epoch.ncsc.mil> + */ + +#include <sys/types.h> +#include <ctype.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <selinux/selinux.h> +#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); +}
diff --git a/libselinux/src/label_android_property.c b/libselinux/src/label_android_property.c new file mode 100644 index 0000000..887e32c --- /dev/null +++ b/libselinux/src/label_android_property.c
@@ -0,0 +1,299 @@ +/* + * Property Service contexts backend for labeling Android + * property keys + */ + +#include <stdarg.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <sys/types.h> +#include <sys/stat.h> +#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); +}
diff --git a/libselinux/src/label_file.c b/libselinux/src/label_file.c new file mode 100644 index 0000000..d3e67c0 --- /dev/null +++ b/libselinux/src/label_file.c
@@ -0,0 +1,859 @@ +/* + * File contexts backend for labeling system + * + * Author : Eamon Walsh <ewalsh@tycho.nsa.gov> + * Author : Stephen Smalley <sds@tycho.nsa.gov> + */ + +#include <assert.h> +#include <fcntl.h> +#include <stdarg.h> +#include <string.h> +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <stdint.h> +#include <unistd.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/stat.h> + +#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) { + if (!regex_version()) { + return -1; + } + len = strlen(regex_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, regex_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, "<<none>>") && 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; + } + + rc = regex_load_mmap(mmap_area, &spec->regex); + if (rc < 0) + goto err; +#ifndef NO_PERSISTENTLY_STORED_PATTERNS + spec->regcomp = 1; +#else + spec->regcomp = 0; +#endif + + 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); + regex_data_free(spec->regex); + } + + 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; + 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; + struct regex_error_data regex_error_data; + + 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; + + /* + * 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, ®ex_error_data) < 0) + goto finish; + if (spec->stem_id == -1) + rc = regex_match(spec->regex, key, partial); + else + rc = regex_match(spec->regex, buf, partial); + if (rc == REGEX_MATCH) { + spec->matches++; + break; + } else if (partial && rc == REGEX_MATCH_PARTIAL) + break; + + if (rc == REGEX_NO_MATCH) + continue; + + errno = ENOENT; + /* else it's an error */ + goto finish; + } + } + + if (i < 0 || strcmp(spec_arr[i].lr.ctx_raw, "<<none>>") == 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) { + if (regex_cmp(spec1->regex, spec2->regex) == SELABEL_INCOMPARABLE){ + 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); +}
diff --git a/libselinux/src/label_file.h b/libselinux/src/label_file.h new file mode 100644 index 0000000..d0ff254 --- /dev/null +++ b/libselinux/src/label_file.h
@@ -0,0 +1,474 @@ +#ifndef _SELABEL_FILE_H_ +#define _SELABEL_FILE_H_ + +#include <errno.h> +#include <string.h> + +#include <sys/stat.h> + +/* + * Android: regex.h/c was introduced to hold all dependencies on the regular + * expression back-end when we started supporting PCRE2. regex.h defines a + * minimal interface required by libselinux, so that the remaining code + * can be agnostic about the underlying implementation. + */ +#include "regex.h" + +#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 + +/* 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 */ + struct regex_data * regex; /* backend dependent regular expression data */ + 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 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, + struct regex_error_data * error_data) +{ + char *reg_buf, *anchored_regex, *cp; + struct stem *stem_arr = data->stem_arr; + size_t len; + int rc; + + 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. */ + rc = regex_prepare_data(&spec->regex, anchored_regex, error_data); + free(anchored_regex); + if (rc < 0) { + 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; + char const *errbuf; + struct regex_error_data error_data; + + 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], &error_data)) { + 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, "<<none>>") && 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_ */
diff --git a/libselinux/src/label_internal.h b/libselinux/src/label_internal.h new file mode 100644 index 0000000..455d948 --- /dev/null +++ b/libselinux/src/label_internal.h
@@ -0,0 +1,93 @@ +/* + * This file describes the internal interface used by the labeler + * for calling the user-supplied memory allocation, validation, + * and locking routine. + * + * Author : Eamon Walsh <ewalsh@epoch.ncsc.mil> + */ +#ifndef _SELABEL_INTERNAL_H_ +#define _SELABEL_INTERNAL_H_ + +#include <stdlib.h> +#include <stdarg.h> +#include <selinux/selinux.h> +#include <selinux/label.h> +#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_ */
diff --git a/libselinux/src/label_support.c b/libselinux/src/label_support.c new file mode 100644 index 0000000..e226d51 --- /dev/null +++ b/libselinux/src/label_support.c
@@ -0,0 +1,117 @@ +/* + * This file contains helper functions for labeling support. + * + * Author : Richard Haines <richard_c_haines@btinternet.com> + */ + +#include <stdlib.h> +#include <stdarg.h> +#include <ctype.h> +#include <string.h> +#include <errno.h> +#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; +}
diff --git a/libselinux/src/lgetfilecon.c b/libselinux/src/lgetfilecon.c new file mode 100644 index 0000000..22851a4 --- /dev/null +++ b/libselinux/src/lgetfilecon.c
@@ -0,0 +1,50 @@ +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <sys/xattr.h> +#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; +}
diff --git a/libselinux/src/load_policy.c b/libselinux/src/load_policy.c new file mode 100644 index 0000000..51a178a --- /dev/null +++ b/libselinux/src/load_policy.c
@@ -0,0 +1,41 @@ +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <sys/mount.h> +#include <sys/utsname.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> +#include <string.h> +#include <errno.h> +#include "selinux_internal.h" +#include <dlfcn.h> +#include "policy.h" +#include <limits.h> +#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 | O_CLOEXEC); + if (fd < 0) + return -1; + + ret = write(fd, data, len); + close(fd); + if (ret < 0) + return -1; + return 0; +} + +hidden_def(security_load_policy)
diff --git a/libselinux/src/lsetfilecon.c b/libselinux/src/lsetfilecon.c new file mode 100644 index 0000000..7147f9e --- /dev/null +++ b/libselinux/src/lsetfilecon.c
@@ -0,0 +1,15 @@ +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <sys/xattr.h> +#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); +} +
diff --git a/libselinux/src/mapping.c b/libselinux/src/mapping.c new file mode 100644 index 0000000..f205804 --- /dev/null +++ b/libselinux/src/mapping.c
@@ -0,0 +1,210 @@ +/* + * Class and permission mappings. + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <selinux/selinux.h> +#include <selinux/avc.h> +#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; i<current_mapping[tclass].num_perms; i++) + if (tperm & (1<<i)) { + kperm |= current_mapping[tclass].perms[i]; + tperm &= ~(1<<i); + } + return kperm; + } + + /* If here no mapping set or the perm requested is not valid. */ + if (current_mapping_size != 0) { + errno = EINVAL; + return 0; + } + else + return tperm; +} + +/* + * Get mapped values from real, kernel values + */ + +security_class_t +map_class(security_class_t kclass) +{ + security_class_t i; + + for (i=0; i<current_mapping_size; i++) + if (current_mapping[i].value == kclass) + return i; + +/* If here no mapping set or the class requested is not valid. */ + if (current_mapping_size != 0) { + errno = EINVAL; + return 0; + } + else + return kclass; +} + +access_vector_t +map_perm(security_class_t tclass, access_vector_t kperm) +{ + if (tclass < current_mapping_size) { + unsigned i; + access_vector_t tperm = 0; + + for (i=0; i<current_mapping[tclass].num_perms; i++) + if (kperm & current_mapping[tclass].perms[i]) { + tperm |= 1<<i; + kperm &= ~current_mapping[tclass].perms[i]; + } + + if (tperm == 0) { + errno = EINVAL; + return 0; + } + else + return tperm; + } + return kperm; +} + +void +map_decision(security_class_t tclass, struct av_decision *avd) +{ + if (tclass < current_mapping_size) { + unsigned i; + access_vector_t result; + + for (i=0, result=0; i<current_mapping[tclass].num_perms; i++) + if (avd->allowed & current_mapping[tclass].perms[i]) + result |= 1<<i; + avd->allowed = result; + + for (i=0, result=0; i<current_mapping[tclass].num_perms; i++) + if (avd->decided & current_mapping[tclass].perms[i]) + result |= 1<<i; + avd->decided = result; + + for (i=0, result=0; i<current_mapping[tclass].num_perms; i++) + if (avd->auditallow & current_mapping[tclass].perms[i]) + result |= 1<<i; + avd->auditallow = result; + + for (i=0, result=0; i<current_mapping[tclass].num_perms; i++) + if (avd->auditdeny & current_mapping[tclass].perms[i]) + result |= 1<<i; + avd->auditdeny = result; + } +}
diff --git a/libselinux/src/mapping.h b/libselinux/src/mapping.h new file mode 100644 index 0000000..b96756b --- /dev/null +++ b/libselinux/src/mapping.h
@@ -0,0 +1,41 @@ +/* + * 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 <selinux/selinux.h> + +/* + * 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_ */
diff --git a/libselinux/src/policy.h b/libselinux/src/policy.h new file mode 100644 index 0000000..92a416e --- /dev/null +++ b/libselinux/src/policy.h
@@ -0,0 +1,28 @@ +#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
diff --git a/libselinux/src/policyvers.c b/libselinux/src/policyvers.c new file mode 100644 index 0000000..284a7f7 --- /dev/null +++ b/libselinux/src/policyvers.c
@@ -0,0 +1,45 @@ +#include <unistd.h> +#include <sys/types.h> +#include <fcntl.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include "selinux_internal.h" +#include <stdio.h> +#include "policy.h" +#include "dso.h" +#include <limits.h> + +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)
diff --git a/libselinux/src/procattr.c b/libselinux/src/procattr.c new file mode 100644 index 0000000..74c0012 --- /dev/null +++ b/libselinux/src/procattr.c
@@ -0,0 +1,176 @@ +#include <sys/syscall.h> +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#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)
diff --git a/libselinux/src/regex.c b/libselinux/src/regex.c new file mode 100644 index 0000000..4223b02 --- /dev/null +++ b/libselinux/src/regex.c
@@ -0,0 +1,390 @@ +#include <assert.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> + +#include "regex.h" +#include "label_file.h" + +int regex_prepare_data(struct regex_data ** regex, char const * pattern_string, + struct regex_error_data * errordata) { + memset(errordata, 0, sizeof(struct regex_error_data)); + *regex = regex_data_create(); + if (!(*regex)) + return -1; +#ifdef USE_PCRE2 + (*regex)->regex = pcre2_compile((PCRE2_SPTR)pattern_string, + PCRE2_ZERO_TERMINATED, + PCRE2_DOTALL, + &errordata->error_code, + &errordata->error_offset, NULL); +#else + (*regex)->regex = pcre_compile(pattern_string, PCRE_DOTALL, + &errordata->error_buffer, + &errordata->error_offset, NULL); +#endif + if (!(*regex)->regex) { + goto err; + } + +#ifdef USE_PCRE2 + (*regex)->match_data = + pcre2_match_data_create_from_pattern((*regex)->regex, NULL); + if (!(*regex)->match_data) { + goto err; + } +#else + (*regex)->sd = pcre_study((*regex)->regex, 0, &errordata->error_buffer); + if (!(*regex)->sd && errordata->error_buffer) { + goto err; + } + (*regex)->extra_owned = !!(*regex)->sd; +#endif + return 0; + +err: regex_data_free(*regex); + *regex = NULL; + return -1; +} + +char const * regex_version() { +#ifdef USE_PCRE2 + static int initialized = 0; + static char * version_string = NULL; + size_t version_string_len; + if (!initialized) { + version_string_len = pcre2_config(PCRE2_CONFIG_VERSION, NULL); + version_string = (char*) malloc(version_string_len); + if (!version_string) { + return NULL; + } + pcre2_config(PCRE2_CONFIG_VERSION, version_string); + initialized = 1; + } + return version_string; +#else + return pcre_version(); +#endif +} + +int regex_load_mmap(struct mmap_area * mmap_area, struct regex_data ** regex) { + int rc; + size_t entry_len, info_len; + + rc = next_entry(&entry_len, mmap_area, sizeof(uint32_t)); +#ifdef USE_PCRE2 + if (rc < 0) + return -1; + +#ifndef NO_PERSISTENTLY_STORED_PATTERNS + /* this should yield exactly one because we store one pattern at a time + */ + rc = pcre2_serialize_get_number_of_codes(mmap_area->next_addr); + if (rc != 1) + return -1; + + *regex = regex_data_create(); + if (!*regex) + return -1; + + rc = pcre2_serialize_decode(&(*regex)->regex, 1, + (PCRE2_SPTR)mmap_area->next_addr, NULL); + if (rc != 1) + goto err; + + (*regex)->match_data = + pcre2_match_data_create_from_pattern((*regex)->regex, NULL); + if (!(*regex)->match_data) + goto err; + +#endif /* NO_PERSISTENTLY_STORED_PATTERNS */ + /* and skip the decoded bit */ + rc = next_entry(NULL, mmap_area, entry_len); + if (rc < 0) + goto err; + + return 0; +#else + if (rc < 0 || !entry_len) { + rc = -1; + return -1; + } + *regex = regex_data_create(); + if (!(*regex)) + return -1; + + (*regex)->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((*regex)->regex, NULL, PCRE_INFO_SIZE, &info_len); + if (rc < 0 || info_len != entry_len) { + goto err; + } + + rc = next_entry(&entry_len, mmap_area, sizeof(uint32_t)); + if (rc < 0 || !entry_len) { + goto err; + } + (*regex)->lsd.study_data = (void *) mmap_area->next_addr; + (*regex)->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((*regex)->regex, &(*regex)->lsd, + PCRE_INFO_STUDYSIZE, + &info_len); + if (rc < 0 || info_len != entry_len) { + goto err; + } + (*regex)->extra_owned = 0; + return 0; +#endif + err: regex_data_free(*regex); + *regex = NULL; + return -1; +} + +int regex_writef(struct regex_data * regex, FILE * fp) { + int rc; + size_t len; +#ifdef USE_PCRE2 + PCRE2_UCHAR * bytes; + PCRE2_SIZE to_write; + +#ifndef NO_PERSISTENTLY_STORED_PATTERNS + /* encode the patter for serialization */ + rc = pcre2_serialize_encode(®ex->regex, 1, &bytes, &to_write, NULL); + if (rc != 1) + return -1; + +#else + to_write = 0; +#endif + /* write serialized pattern's size */ + len = fwrite(&to_write, sizeof(uint32_t), 1, fp); + if (len != 1) { +#ifndef NO_PERSISTENTLY_STORED_PATTERNS + pcre2_serialize_free(bytes); +#endif + return -1; + } + +#ifndef NO_PERSISTENTLY_STORED_PATTERNS + /* write serialized pattern */ + len = fwrite(bytes, 1, to_write, fp); + if (len != to_write) { + pcre2_serialize_free(bytes); + return -1; + } + pcre2_serialize_free(bytes); +#endif +#else + uint32_t to_write; + size_t size; + pcre_extra * sd = regex->extra_owned ? regex->sd : ®ex->lsd; + + /* determine the size of the pcre data in bytes */ + rc = pcre_fullinfo(regex->regex, NULL, PCRE_INFO_SIZE, &size); + if (rc < 0) + return -1; + + /* write the number of bytes in the pcre data */ + to_write = size; + len = fwrite(&to_write, sizeof(uint32_t), 1, fp); + if (len != 1) + return -1; + + /* write the actual pcre data as a char array */ + len = fwrite(regex->regex, 1, to_write, fp); + if (len != to_write) + return -1; + + /* determine the size of the pcre study info */ + rc = pcre_fullinfo(regex->regex, sd, PCRE_INFO_STUDYSIZE, &size); + if (rc < 0) + return -1; + + /* write the number of bytes in the pcre study data */ + to_write = size; + len = fwrite(&to_write, sizeof(uint32_t), 1, fp); + if (len != 1) + return -1; + + /* write the actual pcre study data as a char array */ + len = fwrite(sd->study_data, 1, to_write, fp); + if (len != to_write) + return -1; +#endif + return 0; +} + +struct regex_data * regex_data_create() { + struct regex_data * dummy = (struct regex_data*) malloc( + sizeof(struct regex_data)); + if (dummy) { + memset(dummy, 0, sizeof(struct regex_data)); + } + return dummy; +} + +void regex_data_free(struct regex_data * regex) { + if (regex) { +#ifdef USE_PCRE2 + if (regex->regex) { + pcre2_code_free(regex->regex); + } + if (regex->match_data) { + pcre2_match_data_free(regex->match_data); + } +#else + if (regex->regex) + pcre_free(regex->regex); + if (regex->extra_owned && regex->sd) { + pcre_free_study(regex->sd); + } +#endif + free(regex); + } +} + +int regex_match(struct regex_data * regex, char const * subject, int partial) { + int rc; +#ifdef USE_PCRE2 + rc = pcre2_match(regex->regex, + (PCRE2_SPTR)subject, PCRE2_ZERO_TERMINATED, 0, + partial ? PCRE2_PARTIAL_SOFT : 0, regex->match_data, + NULL); + if (rc > 0) + return REGEX_MATCH; + switch (rc) { + case PCRE2_ERROR_PARTIAL: + return REGEX_MATCH_PARTIAL; + case PCRE2_ERROR_NOMATCH: + return REGEX_NO_MATCH; + default: + return REGEX_ERROR; + } +#else + rc = pcre_exec(regex->regex, + regex->extra_owned ? regex->sd : ®ex->lsd, subject, + strlen(subject), 0, partial ? PCRE_PARTIAL_SOFT : 0, + NULL, + 0); + switch (rc) { + case 0: + return REGEX_MATCH; + case PCRE_ERROR_PARTIAL: + return REGEX_MATCH_PARTIAL; + case PCRE_ERROR_NOMATCH: + return REGEX_NO_MATCH; + default: + return REGEX_ERROR; + } +#endif +} + +/* TODO Replace this compare function with something that actually compares the + * regular expressions. + * This compare function basically just compares the binary representations of + * the automatons, and because this representation contains pointers and + * metadata, it can only return a match if regex1 == regex2. + * Preferably, this function would be replaced with an algorithm that computes + * the equivalence of the automatons systematically. + */ +int regex_cmp(struct regex_data * regex1, struct regex_data * regex2) { + int rc; + size_t len1, len2; +#ifdef USE_PCRE2 + rc = pcre2_pattern_info(regex1->regex, PCRE2_INFO_SIZE, &len1); + assert(rc == 0); + rc = pcre2_pattern_info(regex2->regex, PCRE2_INFO_SIZE, &len2); + assert(rc == 0); + if (len1 != len2 || memcmp(regex1->regex, regex2->regex, len1)) + return SELABEL_INCOMPARABLE; +#else + rc = pcre_fullinfo(regex1->regex, NULL, PCRE_INFO_SIZE, &len1); + assert(rc == 0); + rc = pcre_fullinfo(regex2->regex, NULL, PCRE_INFO_SIZE, &len2); + assert(rc == 0); + if (len1 != len2 || memcmp(regex1->regex, regex2->regex, len1)) + return SELABEL_INCOMPARABLE; +#endif + return SELABEL_EQUAL; +} + +void regex_format_error(struct regex_error_data const * error_data, + char * buffer, size_t buf_size) { + unsigned the_end_length = buf_size > 4 ? 4 : buf_size; + char * ptr = &buffer[buf_size - the_end_length]; + int rc = 0; + size_t pos = 0; + if (!buffer || !buf_size) + return; + rc = snprintf(buffer, buf_size, "REGEX back-end error: "); + if (rc < 0) + /* If snprintf fails it constitutes a logical error that needs + * fixing. + */ + abort(); + + pos += rc; + if (pos >= buf_size) + goto truncated; + + if (error_data->error_offset > 0) { +#ifdef USE_PCRE2 + rc = snprintf(buffer + pos, buf_size - pos, "At offset %lu: ", + error_data->error_offset); +#else + rc = snprintf(buffer + pos, buf_size - pos, "At offset %d: ", + error_data->error_offset); +#endif + if (rc < 0) + abort(); + + } + pos += rc; + if (pos >= buf_size) + goto truncated; + +#ifdef USE_PCRE2 + rc = pcre2_get_error_message(error_data->error_code, + (PCRE2_UCHAR*)(buffer + pos), + buf_size - pos); + if (rc == PCRE2_ERROR_NOMEMORY) + goto truncated; +#else + rc = snprintf(buffer + pos, buf_size - pos, "%s", + error_data->error_buffer); + if (rc < 0) + abort(); + + if ((size_t)rc < strlen(error_data->error_buffer)) + goto truncated; +#endif + + return; + +truncated: + /* replace end of string with "..." to indicate that it was truncated */ + switch (the_end_length) { + /* no break statements, fall-through is intended */ + case 4: + *ptr++ = '.'; + case 3: + *ptr++ = '.'; + case 2: + *ptr++ = '.'; + case 1: + *ptr++ = '\0'; + default: + break; + } + return; +}
diff --git a/libselinux/src/regex.h b/libselinux/src/regex.h new file mode 100644 index 0000000..7b74f32 --- /dev/null +++ b/libselinux/src/regex.h
@@ -0,0 +1,166 @@ +#ifndef SRC_REGEX_H_ +#define SRC_REGEX_H_ + +#include <stdio.h> + +#ifdef USE_PCRE2 +#include <pcre2.h> +#else +#include <pcre.h> +#endif + +enum { + REGEX_MATCH, + REGEX_MATCH_PARTIAL, + REGEX_NO_MATCH, + REGEX_ERROR = -1, +}; + +#ifdef USE_PCRE2 +struct regex_data { + pcre2_code * regex; /* compiled regular expression */ + pcre2_match_data * match_data; /* match data block required for the compiled + pattern in regex2 */ +}; + +struct regex_error_data { + int error_code; + PCRE2_SIZE error_offset; +}; + +/* ^^^^^^ USE_PCRE2 ^^^^^^ */ +#else +/* vvvvvv USE_PCRE vvvvvv */ + +/* 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 + +struct regex_data { + pcre *regex; /* compiled regular expression */ + int extra_owned; + union { + pcre_extra *sd; /* pointer to extra compiled stuff */ + pcre_extra lsd; /* used to hold the mmap'd version */ + }; +}; + +struct regex_error_data { + char const * error_buffer; + int error_offset; +}; + +#endif /* USE_PCRE2 */ + +struct mmap_area; + +/** + * regex_verison returns the version string of the underlying regular + * regular expressions library. In the case of PCRE it just returns the + * result of pcre_version(). In the case of PCRE2, the very first time this + * function is called it allocates a buffer large enough to hold the version + * string and reads the PCRE2_CONFIG_VERSION option to fill the buffer. + * The allocated buffer will linger in memory until the calling process is being + * reaped. + * + * It may return NULL on error. + */ +char const * regex_version(); +/** + * This constructor function allocates a buffer for a regex_data structure. + * The buffer is being initialized with zeroes. + */ +struct regex_data * regex_data_create(); +/** + * This complementary destructor function frees the a given regex_data buffer. + * It also frees any non NULL member pointers with the appropriate pcreX_X_free + * function. For PCRE this function respects the extra_owned field and frees + * the pcre_extra data conditionally. Calling this function on a NULL pointer is + * save. + */ +void regex_data_free(struct regex_data * regex); +/** + * This function compiles the regular expression. Additionally, it prepares + * data structures required by the different underlying engines. For PCRE + * it calls pcre_study to generate optional data required for optimized + * execution of the compiled pattern. In the case of PCRE2, it allocates + * a pcre2_match_data structure of appropriate size to hold all possible + * matches created by the pattern. + * + * @arg regex If successful, the structure returned through *regex was allocated + * with regex_data_create and must be freed with regex_data_free. + * @arg pattern_string The pattern string that is to be compiled. + * @arg errordata A pointer to a regex_error_data structure must be passed + * to this function. This structure depends on the underlying + * implementation. It can be passed to regex_format_error + * to generate a human readable error message. + * @retval 0 on success + * @retval -1 on error + */ +int regex_prepare_data(struct regex_data ** regex, char const * pattern_string, + struct regex_error_data * errordata); +/** + * This function loads a serialized precompiled pattern from a contiguous + * data region given by map_area. + * + * @arg map_area Description of the memory region holding a serialized + * representation of the precompiled pattern. + * @arg regex If successful, the structure returned through *regex was allocated + * with regex_data_create and must be freed with regex_data_free. + * + * @retval 0 on success + * @retval -1 on error + */ +int regex_load_mmap(struct mmap_area * map_area, struct regex_data ** regex); +/** + * This function stores a precompiled regular expression to a file. + * In the case of PCRE, it just dumps the binary representation of the + * precomplied pattern into a file. In the case of PCRE2, it uses the + * serialization function provided by the library. + * + * @arg regex The precomplied regular expression data. + * @arg fp A file stream specifying the output file. + */ +int regex_writef(struct regex_data * regex, FILE * fp); +/** + * This function applies a precompiled pattern to a subject string and + * returns whether or not a match was found. + * + * @arg regex The precompiled pattern. + * @arg subject The subject string. + * @arg partial Boolean indicating if partial matches are wanted. A nonzero + * value is equivalent to specifying PCRE[2]_PARTIAL_SOFT as + * option to pcre_exec of pcre2_match. + * @retval REGEX_MATCH if a match was found + * @retval REGEX_MATCH_PARTIAL if a partial match was found + * @retval REGEX_NO_MATCH if no match was found + * @retval REGEX_ERROR if an error was encountered during the execution of the + * regular expression + */ +int regex_match(struct regex_data * regex, char const * subject, int partial); +/** + * This function compares two compiled regular expressions (regex1 and regex2). + * It compares the binary representations of the compiled patterns. It is a very + * crude approximation because the binary representation holds data like + * reference counters, that has nothing to do with the actual state machine. + * + * @retval SELABEL_EQUAL if the pattern's binary representations are exactly + * the same + * @retval SELABEL_INCOMPARABLE otherwise + */ +int regex_cmp(struct regex_data * regex1, struct regex_data * regex2); +/** + * This function takes the error data returned by regex_prepare_data and turns + * it in to a human readable error message. + * If the buffer given to hold the error message is to small it truncates the + * message and indicates the truncation with an ellipsis ("...") at the end of + * the buffer. + * + * @arg error_data Error data as returned by regex_prepare_data. + * @arg buffer String buffer to hold the formated error string. + * @arg buf_size Total size of the given bufer in bytes. + */ +void regex_format_error(struct regex_error_data const * error_data, + char * buffer, size_t buf_size); +#endif /* SRC_REGEX_H_ */
diff --git a/libselinux/src/selinux_internal.h b/libselinux/src/selinux_internal.h new file mode 100644 index 0000000..5087bb6 --- /dev/null +++ b/libselinux/src/selinux_internal.h
@@ -0,0 +1,132 @@ +#include <selinux/selinux.h> +#include <pthread.h> +#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) + +
diff --git a/libselinux/src/selinux_netlink.h b/libselinux/src/selinux_netlink.h new file mode 100644 index 0000000..88ef551 --- /dev/null +++ b/libselinux/src/selinux_netlink.h
@@ -0,0 +1,31 @@ +/* + * Netlink event notifications for SELinux. + * + * Author: James Morris <jmorris@redhat.com> + */ +#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 */
diff --git a/libselinux/src/sestatus.c b/libselinux/src/sestatus.c new file mode 100644 index 0000000..ed29dc5 --- /dev/null +++ b/libselinux/src/sestatus.c
@@ -0,0 +1,349 @@ +/* + * sestatus.c + * + * APIs to reference SELinux kernel status page (/selinux/status) + * + * Author: KaiGai Kohei <kaigai@ak.jp.nec.com> + * + */ +#include <fcntl.h> +#include <limits.h> +#include <sched.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#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); +}
diff --git a/libselinux/src/setenforce.c b/libselinux/src/setenforce.c new file mode 100644 index 0000000..e5e7612 --- /dev/null +++ b/libselinux/src/setenforce.c
@@ -0,0 +1,37 @@ +#include <unistd.h> +#include <sys/types.h> +#include <fcntl.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include "selinux_internal.h" +#include "policy.h" +#include <stdio.h> +#include <limits.h> + +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)
diff --git a/libselinux/src/setfilecon.c b/libselinux/src/setfilecon.c new file mode 100644 index 0000000..81322f8 --- /dev/null +++ b/libselinux/src/setfilecon.c
@@ -0,0 +1,15 @@ +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <sys/xattr.h> +#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); +} +
diff --git a/libselinux/src/stringrep.c b/libselinux/src/stringrep.c new file mode 100644 index 0000000..c867222 --- /dev/null +++ b/libselinux/src/stringrep.c
@@ -0,0 +1,277 @@ +/* + * String representation support for classes and permissions. + */ +#include <sys/stat.h> +#include <dirent.h> +#include <fcntl.h> +#include <limits.h> +#include <unistd.h> +#include <errno.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <ctype.h> +#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; i<MAXVECTORS; i++) + free(node->perms[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; i<MAXVECTORS && node->perms[i] != NULL; i++) + if (strcmp(node->perms[i],s) == 0) + return map_perm(tclass, 1<<i); + } + + errno = EINVAL; + return 0; +} + +const char *security_class_to_string(security_class_t tclass) +{ + struct discover_class_node *node; + + tclass = unmap_class(tclass); + + node = get_class_cache_entry_value(tclass); + if (node) + return node->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; i<MAXVECTORS; i++) + if ((1<<i) & av) + return node->perms[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<<i)); + if (str) + len += strlen(str) + 1; + else { + rc = -1; + errno = EINVAL; + goto out; + } + } + tmp >>= 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<<i))); + tmp >>= 1; + i++; + } + sprintf(ptr, "}"); +out: + return rc; +}
diff --git a/libselinux/utils/sefcontext_compile.c b/libselinux/utils/sefcontext_compile.c new file mode 100644 index 0000000..f88c756 --- /dev/null +++ b/libselinux/utils/sefcontext_compile.c
@@ -0,0 +1,363 @@ +#include <ctype.h> +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <getopt.h> +#include <limits.h> +#include <selinux/selinux.h> + +#include "../src/label_file.h" +#include "../src/regex.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 version of the regex back-end */ + if (!regex_version()) + goto err; + section_len = strlen(regex_version()); + len = fwrite(§ion_len, sizeof(uint32_t), 1, bin_file); + if (len != 1) + goto err; + len = fwrite(regex_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; + struct regex_data *re = specs[i].regex; + 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; + + /* Write regex related data */ + rc = regex_writef(re, bin_file); + if (rc < 0) + 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); + regex_data_free(specs[i].regex); + } + 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; +}