| /* | 
 |  * security/tomoyo/realpath.c | 
 |  * | 
 |  * Get the canonicalized absolute pathnames. The basis for TOMOYO. | 
 |  * | 
 |  * Copyright (C) 2005-2009  NTT DATA CORPORATION | 
 |  * | 
 |  * Version: 2.2.0   2009/04/01 | 
 |  * | 
 |  */ | 
 |  | 
 | #include <linux/types.h> | 
 | #include <linux/mount.h> | 
 | #include <linux/mnt_namespace.h> | 
 | #include <linux/fs_struct.h> | 
 | #include "common.h" | 
 | #include "realpath.h" | 
 |  | 
 | /** | 
 |  * tomoyo_encode: Convert binary string to ascii string. | 
 |  * | 
 |  * @buffer:  Buffer for ASCII string. | 
 |  * @buflen:  Size of @buffer. | 
 |  * @str:     Binary string. | 
 |  * | 
 |  * Returns 0 on success, -ENOMEM otherwise. | 
 |  */ | 
 | int tomoyo_encode(char *buffer, int buflen, const char *str) | 
 | { | 
 | 	while (1) { | 
 | 		const unsigned char c = *(unsigned char *) str++; | 
 |  | 
 | 		if (tomoyo_is_valid(c)) { | 
 | 			if (--buflen <= 0) | 
 | 				break; | 
 | 			*buffer++ = (char) c; | 
 | 			if (c != '\\') | 
 | 				continue; | 
 | 			if (--buflen <= 0) | 
 | 				break; | 
 | 			*buffer++ = (char) c; | 
 | 			continue; | 
 | 		} | 
 | 		if (!c) { | 
 | 			if (--buflen <= 0) | 
 | 				break; | 
 | 			*buffer = '\0'; | 
 | 			return 0; | 
 | 		} | 
 | 		buflen -= 4; | 
 | 		if (buflen <= 0) | 
 | 			break; | 
 | 		*buffer++ = '\\'; | 
 | 		*buffer++ = (c >> 6) + '0'; | 
 | 		*buffer++ = ((c >> 3) & 7) + '0'; | 
 | 		*buffer++ = (c & 7) + '0'; | 
 | 	} | 
 | 	return -ENOMEM; | 
 | } | 
 |  | 
 | /** | 
 |  * tomoyo_realpath_from_path2 - Returns realpath(3) of the given dentry but ignores chroot'ed root. | 
 |  * | 
 |  * @path:        Pointer to "struct path". | 
 |  * @newname:     Pointer to buffer to return value in. | 
 |  * @newname_len: Size of @newname. | 
 |  * | 
 |  * Returns 0 on success, negative value otherwise. | 
 |  * | 
 |  * If dentry is a directory, trailing '/' is appended. | 
 |  * Characters out of 0x20 < c < 0x7F range are converted to | 
 |  * \ooo style octal string. | 
 |  * Character \ is converted to \\ string. | 
 |  */ | 
 | int tomoyo_realpath_from_path2(struct path *path, char *newname, | 
 | 			       int newname_len) | 
 | { | 
 | 	int error = -ENOMEM; | 
 | 	struct dentry *dentry = path->dentry; | 
 | 	char *sp; | 
 |  | 
 | 	if (!dentry || !path->mnt || !newname || newname_len <= 2048) | 
 | 		return -EINVAL; | 
 | 	if (dentry->d_op && dentry->d_op->d_dname) { | 
 | 		/* For "socket:[\$]" and "pipe:[\$]". */ | 
 | 		static const int offset = 1536; | 
 | 		sp = dentry->d_op->d_dname(dentry, newname + offset, | 
 | 					   newname_len - offset); | 
 | 	} else { | 
 | 		/* Taken from d_namespace_path(). */ | 
 | 		struct path root; | 
 | 		struct path ns_root = { }; | 
 | 		struct path tmp; | 
 |  | 
 | 		read_lock(¤t->fs->lock); | 
 | 		root = current->fs->root; | 
 | 		path_get(&root); | 
 | 		read_unlock(¤t->fs->lock); | 
 | 		spin_lock(&vfsmount_lock); | 
 | 		if (root.mnt && root.mnt->mnt_ns) | 
 | 			ns_root.mnt = mntget(root.mnt->mnt_ns->root); | 
 | 		if (ns_root.mnt) | 
 | 			ns_root.dentry = dget(ns_root.mnt->mnt_root); | 
 | 		spin_unlock(&vfsmount_lock); | 
 | 		spin_lock(&dcache_lock); | 
 | 		tmp = ns_root; | 
 | 		sp = __d_path(path, &tmp, newname, newname_len); | 
 | 		spin_unlock(&dcache_lock); | 
 | 		path_put(&root); | 
 | 		path_put(&ns_root); | 
 | 	} | 
 | 	if (IS_ERR(sp)) | 
 | 		error = PTR_ERR(sp); | 
 | 	else | 
 | 		error = tomoyo_encode(newname, sp - newname, sp); | 
 | 	/* Append trailing '/' if dentry is a directory. */ | 
 | 	if (!error && dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode) | 
 | 	    && *newname) { | 
 | 		sp = newname + strlen(newname); | 
 | 		if (*(sp - 1) != '/') { | 
 | 			if (sp < newname + newname_len - 4) { | 
 | 				*sp++ = '/'; | 
 | 				*sp = '\0'; | 
 | 			} else { | 
 | 				error = -ENOMEM; | 
 | 			} | 
 | 		} | 
 | 	} | 
 | 	if (error) | 
 | 		printk(KERN_WARNING "tomoyo_realpath: Pathname too long.\n"); | 
 | 	return error; | 
 | } | 
 |  | 
 | /** | 
 |  * tomoyo_realpath_from_path - Returns realpath(3) of the given pathname but ignores chroot'ed root. | 
 |  * | 
 |  * @path: Pointer to "struct path". | 
 |  * | 
 |  * Returns the realpath of the given @path on success, NULL otherwise. | 
 |  * | 
 |  * These functions use tomoyo_alloc(), so the caller must call tomoyo_free() | 
 |  * if these functions didn't return NULL. | 
 |  */ | 
 | char *tomoyo_realpath_from_path(struct path *path) | 
 | { | 
 | 	char *buf = tomoyo_alloc(sizeof(struct tomoyo_page_buffer)); | 
 |  | 
 | 	BUILD_BUG_ON(sizeof(struct tomoyo_page_buffer) | 
 | 		     <= TOMOYO_MAX_PATHNAME_LEN - 1); | 
 | 	if (!buf) | 
 | 		return NULL; | 
 | 	if (tomoyo_realpath_from_path2(path, buf, | 
 | 				       TOMOYO_MAX_PATHNAME_LEN - 1) == 0) | 
 | 		return buf; | 
 | 	tomoyo_free(buf); | 
 | 	return NULL; | 
 | } | 
 |  | 
 | /** | 
 |  * tomoyo_realpath - Get realpath of a pathname. | 
 |  * | 
 |  * @pathname: The pathname to solve. | 
 |  * | 
 |  * Returns the realpath of @pathname on success, NULL otherwise. | 
 |  */ | 
 | char *tomoyo_realpath(const char *pathname) | 
 | { | 
 | 	struct nameidata nd; | 
 |  | 
 | 	if (pathname && path_lookup(pathname, LOOKUP_FOLLOW, &nd) == 0) { | 
 | 		char *buf = tomoyo_realpath_from_path(&nd.path); | 
 | 		path_put(&nd.path); | 
 | 		return buf; | 
 | 	} | 
 | 	return NULL; | 
 | } | 
 |  | 
 | /** | 
 |  * tomoyo_realpath_nofollow - Get realpath of a pathname. | 
 |  * | 
 |  * @pathname: The pathname to solve. | 
 |  * | 
 |  * Returns the realpath of @pathname on success, NULL otherwise. | 
 |  */ | 
 | char *tomoyo_realpath_nofollow(const char *pathname) | 
 | { | 
 | 	struct nameidata nd; | 
 |  | 
 | 	if (pathname && path_lookup(pathname, 0, &nd) == 0) { | 
 | 		char *buf = tomoyo_realpath_from_path(&nd.path); | 
 | 		path_put(&nd.path); | 
 | 		return buf; | 
 | 	} | 
 | 	return NULL; | 
 | } | 
 |  | 
 | /* Memory allocated for non-string data. */ | 
 | static unsigned int tomoyo_allocated_memory_for_elements; | 
 | /* Quota for holding non-string data. */ | 
 | static unsigned int tomoyo_quota_for_elements; | 
 |  | 
 | /** | 
 |  * tomoyo_alloc_element - Allocate permanent memory for structures. | 
 |  * | 
 |  * @size: Size in bytes. | 
 |  * | 
 |  * Returns pointer to allocated memory on success, NULL otherwise. | 
 |  * | 
 |  * Memory has to be zeroed. | 
 |  * The RAM is chunked, so NEVER try to kfree() the returned pointer. | 
 |  */ | 
 | void *tomoyo_alloc_element(const unsigned int size) | 
 | { | 
 | 	static char *buf; | 
 | 	static DEFINE_MUTEX(lock); | 
 | 	static unsigned int buf_used_len = PATH_MAX; | 
 | 	char *ptr = NULL; | 
 | 	/*Assumes sizeof(void *) >= sizeof(long) is true. */ | 
 | 	const unsigned int word_aligned_size | 
 | 		= roundup(size, max(sizeof(void *), sizeof(long))); | 
 | 	if (word_aligned_size > PATH_MAX) | 
 | 		return NULL; | 
 | 	/***** EXCLUSIVE SECTION START *****/ | 
 | 	mutex_lock(&lock); | 
 | 	if (buf_used_len + word_aligned_size > PATH_MAX) { | 
 | 		if (!tomoyo_quota_for_elements || | 
 | 		    tomoyo_allocated_memory_for_elements | 
 | 		    + PATH_MAX <= tomoyo_quota_for_elements) | 
 | 			ptr = kzalloc(PATH_MAX, GFP_KERNEL); | 
 | 		if (!ptr) { | 
 | 			printk(KERN_WARNING "ERROR: Out of memory " | 
 | 			       "for tomoyo_alloc_element().\n"); | 
 | 			if (!tomoyo_policy_loaded) | 
 | 				panic("MAC Initialization failed.\n"); | 
 | 		} else { | 
 | 			buf = ptr; | 
 | 			tomoyo_allocated_memory_for_elements += PATH_MAX; | 
 | 			buf_used_len = word_aligned_size; | 
 | 			ptr = buf; | 
 | 		} | 
 | 	} else if (word_aligned_size) { | 
 | 		int i; | 
 | 		ptr = buf + buf_used_len; | 
 | 		buf_used_len += word_aligned_size; | 
 | 		for (i = 0; i < word_aligned_size; i++) { | 
 | 			if (!ptr[i]) | 
 | 				continue; | 
 | 			printk(KERN_ERR "WARNING: Reserved memory was tainted! " | 
 | 			       "The system might go wrong.\n"); | 
 | 			ptr[i] = '\0'; | 
 | 		} | 
 | 	} | 
 | 	mutex_unlock(&lock); | 
 | 	/***** EXCLUSIVE SECTION END *****/ | 
 | 	return ptr; | 
 | } | 
 |  | 
 | /* Memory allocated for string data in bytes. */ | 
 | static unsigned int tomoyo_allocated_memory_for_savename; | 
 | /* Quota for holding string data in bytes. */ | 
 | static unsigned int tomoyo_quota_for_savename; | 
 |  | 
 | /* | 
 |  * TOMOYO uses this hash only when appending a string into the string | 
 |  * table. Frequency of appending strings is very low. So we don't need | 
 |  * large (e.g. 64k) hash size. 256 will be sufficient. | 
 |  */ | 
 | #define TOMOYO_MAX_HASH 256 | 
 |  | 
 | /* Structure for string data. */ | 
 | struct tomoyo_name_entry { | 
 | 	struct list_head list; | 
 | 	struct tomoyo_path_info entry; | 
 | }; | 
 |  | 
 | /* Structure for available memory region. */ | 
 | struct tomoyo_free_memory_block_list { | 
 | 	struct list_head list; | 
 | 	char *ptr;             /* Pointer to a free area. */ | 
 | 	int len;               /* Length of the area.     */ | 
 | }; | 
 |  | 
 | /* | 
 |  * The list for "struct tomoyo_name_entry". | 
 |  * | 
 |  * This list is updated only inside tomoyo_save_name(), thus | 
 |  * no global mutex exists. | 
 |  */ | 
 | static struct list_head tomoyo_name_list[TOMOYO_MAX_HASH]; | 
 |  | 
 | /** | 
 |  * tomoyo_save_name - Allocate permanent memory for string data. | 
 |  * | 
 |  * @name: The string to store into the permernent memory. | 
 |  * | 
 |  * Returns pointer to "struct tomoyo_path_info" on success, NULL otherwise. | 
 |  * | 
 |  * The RAM is shared, so NEVER try to modify or kfree() the returned name. | 
 |  */ | 
 | const struct tomoyo_path_info *tomoyo_save_name(const char *name) | 
 | { | 
 | 	static LIST_HEAD(fmb_list); | 
 | 	static DEFINE_MUTEX(lock); | 
 | 	struct tomoyo_name_entry *ptr; | 
 | 	unsigned int hash; | 
 | 	/* fmb contains available size in bytes. | 
 | 	   fmb is removed from the fmb_list when fmb->len becomes 0. */ | 
 | 	struct tomoyo_free_memory_block_list *fmb; | 
 | 	int len; | 
 | 	char *cp; | 
 |  | 
 | 	if (!name) | 
 | 		return NULL; | 
 | 	len = strlen(name) + 1; | 
 | 	if (len > TOMOYO_MAX_PATHNAME_LEN) { | 
 | 		printk(KERN_WARNING "ERROR: Name too long " | 
 | 		       "for tomoyo_save_name().\n"); | 
 | 		return NULL; | 
 | 	} | 
 | 	hash = full_name_hash((const unsigned char *) name, len - 1); | 
 | 	/***** EXCLUSIVE SECTION START *****/ | 
 | 	mutex_lock(&lock); | 
 | 	list_for_each_entry(ptr, &tomoyo_name_list[hash % TOMOYO_MAX_HASH], | 
 | 			     list) { | 
 | 		if (hash == ptr->entry.hash && !strcmp(name, ptr->entry.name)) | 
 | 			goto out; | 
 | 	} | 
 | 	list_for_each_entry(fmb, &fmb_list, list) { | 
 | 		if (len <= fmb->len) | 
 | 			goto ready; | 
 | 	} | 
 | 	if (!tomoyo_quota_for_savename || | 
 | 	    tomoyo_allocated_memory_for_savename + PATH_MAX | 
 | 	    <= tomoyo_quota_for_savename) | 
 | 		cp = kzalloc(PATH_MAX, GFP_KERNEL); | 
 | 	else | 
 | 		cp = NULL; | 
 | 	fmb = kzalloc(sizeof(*fmb), GFP_KERNEL); | 
 | 	if (!cp || !fmb) { | 
 | 		kfree(cp); | 
 | 		kfree(fmb); | 
 | 		printk(KERN_WARNING "ERROR: Out of memory " | 
 | 		       "for tomoyo_save_name().\n"); | 
 | 		if (!tomoyo_policy_loaded) | 
 | 			panic("MAC Initialization failed.\n"); | 
 | 		ptr = NULL; | 
 | 		goto out; | 
 | 	} | 
 | 	tomoyo_allocated_memory_for_savename += PATH_MAX; | 
 | 	list_add(&fmb->list, &fmb_list); | 
 | 	fmb->ptr = cp; | 
 | 	fmb->len = PATH_MAX; | 
 |  ready: | 
 | 	ptr = tomoyo_alloc_element(sizeof(*ptr)); | 
 | 	if (!ptr) | 
 | 		goto out; | 
 | 	ptr->entry.name = fmb->ptr; | 
 | 	memmove(fmb->ptr, name, len); | 
 | 	tomoyo_fill_path_info(&ptr->entry); | 
 | 	fmb->ptr += len; | 
 | 	fmb->len -= len; | 
 | 	list_add_tail(&ptr->list, &tomoyo_name_list[hash % TOMOYO_MAX_HASH]); | 
 | 	if (fmb->len == 0) { | 
 | 		list_del(&fmb->list); | 
 | 		kfree(fmb); | 
 | 	} | 
 |  out: | 
 | 	mutex_unlock(&lock); | 
 | 	/***** EXCLUSIVE SECTION END *****/ | 
 | 	return ptr ? &ptr->entry : NULL; | 
 | } | 
 |  | 
 | /** | 
 |  * tomoyo_realpath_init - Initialize realpath related code. | 
 |  */ | 
 | void __init tomoyo_realpath_init(void) | 
 | { | 
 | 	int i; | 
 |  | 
 | 	BUILD_BUG_ON(TOMOYO_MAX_PATHNAME_LEN > PATH_MAX); | 
 | 	for (i = 0; i < TOMOYO_MAX_HASH; i++) | 
 | 		INIT_LIST_HEAD(&tomoyo_name_list[i]); | 
 | 	INIT_LIST_HEAD(&tomoyo_kernel_domain.acl_info_list); | 
 | 	tomoyo_kernel_domain.domainname = tomoyo_save_name(TOMOYO_ROOT_NAME); | 
 | 	list_add_tail(&tomoyo_kernel_domain.list, &tomoyo_domain_list); | 
 | 	down_read(&tomoyo_domain_list_lock); | 
 | 	if (tomoyo_find_domain(TOMOYO_ROOT_NAME) != &tomoyo_kernel_domain) | 
 | 		panic("Can't register tomoyo_kernel_domain"); | 
 | 	up_read(&tomoyo_domain_list_lock); | 
 | } | 
 |  | 
 | /* Memory allocated for temporary purpose. */ | 
 | static atomic_t tomoyo_dynamic_memory_size; | 
 |  | 
 | /** | 
 |  * tomoyo_alloc - Allocate memory for temporary purpose. | 
 |  * | 
 |  * @size: Size in bytes. | 
 |  * | 
 |  * Returns pointer to allocated memory on success, NULL otherwise. | 
 |  */ | 
 | void *tomoyo_alloc(const size_t size) | 
 | { | 
 | 	void *p = kzalloc(size, GFP_KERNEL); | 
 | 	if (p) | 
 | 		atomic_add(ksize(p), &tomoyo_dynamic_memory_size); | 
 | 	return p; | 
 | } | 
 |  | 
 | /** | 
 |  * tomoyo_free - Release memory allocated by tomoyo_alloc(). | 
 |  * | 
 |  * @p: Pointer returned by tomoyo_alloc(). May be NULL. | 
 |  * | 
 |  * Returns nothing. | 
 |  */ | 
 | void tomoyo_free(const void *p) | 
 | { | 
 | 	if (p) { | 
 | 		atomic_sub(ksize(p), &tomoyo_dynamic_memory_size); | 
 | 		kfree(p); | 
 | 	} | 
 | } | 
 |  | 
 | /** | 
 |  * tomoyo_read_memory_counter - Check for memory usage in bytes. | 
 |  * | 
 |  * @head: Pointer to "struct tomoyo_io_buffer". | 
 |  * | 
 |  * Returns memory usage. | 
 |  */ | 
 | int tomoyo_read_memory_counter(struct tomoyo_io_buffer *head) | 
 | { | 
 | 	if (!head->read_eof) { | 
 | 		const unsigned int shared | 
 | 			= tomoyo_allocated_memory_for_savename; | 
 | 		const unsigned int private | 
 | 			= tomoyo_allocated_memory_for_elements; | 
 | 		const unsigned int dynamic | 
 | 			= atomic_read(&tomoyo_dynamic_memory_size); | 
 | 		char buffer[64]; | 
 |  | 
 | 		memset(buffer, 0, sizeof(buffer)); | 
 | 		if (tomoyo_quota_for_savename) | 
 | 			snprintf(buffer, sizeof(buffer) - 1, | 
 | 				 "   (Quota: %10u)", | 
 | 				 tomoyo_quota_for_savename); | 
 | 		else | 
 | 			buffer[0] = '\0'; | 
 | 		tomoyo_io_printf(head, "Shared:  %10u%s\n", shared, buffer); | 
 | 		if (tomoyo_quota_for_elements) | 
 | 			snprintf(buffer, sizeof(buffer) - 1, | 
 | 				 "   (Quota: %10u)", | 
 | 				 tomoyo_quota_for_elements); | 
 | 		else | 
 | 			buffer[0] = '\0'; | 
 | 		tomoyo_io_printf(head, "Private: %10u%s\n", private, buffer); | 
 | 		tomoyo_io_printf(head, "Dynamic: %10u\n", dynamic); | 
 | 		tomoyo_io_printf(head, "Total:   %10u\n", | 
 | 				 shared + private + dynamic); | 
 | 		head->read_eof = true; | 
 | 	} | 
 | 	return 0; | 
 | } | 
 |  | 
 | /** | 
 |  * tomoyo_write_memory_quota - Set memory quota. | 
 |  * | 
 |  * @head: Pointer to "struct tomoyo_io_buffer". | 
 |  * | 
 |  * Returns 0. | 
 |  */ | 
 | int tomoyo_write_memory_quota(struct tomoyo_io_buffer *head) | 
 | { | 
 | 	char *data = head->write_buf; | 
 | 	unsigned int size; | 
 |  | 
 | 	if (sscanf(data, "Shared: %u", &size) == 1) | 
 | 		tomoyo_quota_for_savename = size; | 
 | 	else if (sscanf(data, "Private: %u", &size) == 1) | 
 | 		tomoyo_quota_for_elements = size; | 
 | 	return 0; | 
 | } |