ext4 crypto: add encryption policy and password salt support
Signed-off-by: Michael Halcrow <mhalcrow@google.com> Signed-off-by: Theodore Ts'o <tytso@mit.edu> Signed-off-by: Ildar Muslukhov <muslukhovi@gmail.com>
This commit is contained in:
		
							parent
							
								
									887e2c4522
								
							
						
					
					
						commit
						9bd8212f98
					
				| @ -12,3 +12,4 @@ ext4-y	:= balloc.o bitmap.o dir.o file.o fsync.o ialloc.o inode.o page-io.o \ | |||||||
| 
 | 
 | ||||||
| ext4-$(CONFIG_EXT4_FS_POSIX_ACL)	+= acl.o | ext4-$(CONFIG_EXT4_FS_POSIX_ACL)	+= acl.o | ||||||
| ext4-$(CONFIG_EXT4_FS_SECURITY)		+= xattr_security.o | ext4-$(CONFIG_EXT4_FS_SECURITY)		+= xattr_security.o | ||||||
|  | ext4-$(CONFIG_EXT4_FS_ENCRYPTION)	+= crypto_policy.o | ||||||
|  | |||||||
							
								
								
									
										167
									
								
								fs/ext4/crypto_policy.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								fs/ext4/crypto_policy.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,167 @@ | |||||||
|  | /*
 | ||||||
|  |  * linux/fs/ext4/crypto_policy.c | ||||||
|  |  * | ||||||
|  |  * Copyright (C) 2015, Google, Inc. | ||||||
|  |  * | ||||||
|  |  * This contains encryption policy functions for ext4 | ||||||
|  |  * | ||||||
|  |  * Written by Michael Halcrow, 2015. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #include <linux/random.h> | ||||||
|  | #include <linux/string.h> | ||||||
|  | #include <linux/types.h> | ||||||
|  | 
 | ||||||
|  | #include "ext4.h" | ||||||
|  | #include "xattr.h" | ||||||
|  | 
 | ||||||
|  | static int ext4_inode_has_encryption_context(struct inode *inode) | ||||||
|  | { | ||||||
|  | 	int res = ext4_xattr_get(inode, EXT4_XATTR_INDEX_ENCRYPTION, | ||||||
|  | 				 EXT4_XATTR_NAME_ENCRYPTION_CONTEXT, NULL, 0); | ||||||
|  | 	return (res > 0); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * check whether the policy is consistent with the encryption context | ||||||
|  |  * for the inode | ||||||
|  |  */ | ||||||
|  | static int ext4_is_encryption_context_consistent_with_policy( | ||||||
|  | 	struct inode *inode, const struct ext4_encryption_policy *policy) | ||||||
|  | { | ||||||
|  | 	struct ext4_encryption_context ctx; | ||||||
|  | 	int res = ext4_xattr_get(inode, EXT4_XATTR_INDEX_ENCRYPTION, | ||||||
|  | 				 EXT4_XATTR_NAME_ENCRYPTION_CONTEXT, &ctx, | ||||||
|  | 				 sizeof(ctx)); | ||||||
|  | 	if (res != sizeof(ctx)) | ||||||
|  | 		return 0; | ||||||
|  | 	return (memcmp(ctx.master_key_descriptor, policy->master_key_descriptor, | ||||||
|  | 			EXT4_KEY_DESCRIPTOR_SIZE) == 0 && | ||||||
|  | 		(ctx.contents_encryption_mode == | ||||||
|  | 		 policy->contents_encryption_mode) && | ||||||
|  | 		(ctx.filenames_encryption_mode == | ||||||
|  | 		 policy->filenames_encryption_mode)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int ext4_create_encryption_context_from_policy( | ||||||
|  | 	struct inode *inode, const struct ext4_encryption_policy *policy) | ||||||
|  | { | ||||||
|  | 	struct ext4_encryption_context ctx; | ||||||
|  | 	int res = 0; | ||||||
|  | 
 | ||||||
|  | 	ctx.format = EXT4_ENCRYPTION_CONTEXT_FORMAT_V1; | ||||||
|  | 	memcpy(ctx.master_key_descriptor, policy->master_key_descriptor, | ||||||
|  | 	       EXT4_KEY_DESCRIPTOR_SIZE); | ||||||
|  | 	ctx.contents_encryption_mode = policy->contents_encryption_mode; | ||||||
|  | 	ctx.filenames_encryption_mode = policy->filenames_encryption_mode; | ||||||
|  | 	BUILD_BUG_ON(sizeof(ctx.nonce) != EXT4_KEY_DERIVATION_NONCE_SIZE); | ||||||
|  | 	get_random_bytes(ctx.nonce, EXT4_KEY_DERIVATION_NONCE_SIZE); | ||||||
|  | 
 | ||||||
|  | 	res = ext4_xattr_set(inode, EXT4_XATTR_INDEX_ENCRYPTION, | ||||||
|  | 			     EXT4_XATTR_NAME_ENCRYPTION_CONTEXT, &ctx, | ||||||
|  | 			     sizeof(ctx), 0); | ||||||
|  | 	if (!res) | ||||||
|  | 		ext4_set_inode_flag(inode, EXT4_INODE_ENCRYPT); | ||||||
|  | 	return res; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int ext4_process_policy(const struct ext4_encryption_policy *policy, | ||||||
|  | 			struct inode *inode) | ||||||
|  | { | ||||||
|  | 	if (policy->version != 0) | ||||||
|  | 		return -EINVAL; | ||||||
|  | 
 | ||||||
|  | 	if (!ext4_inode_has_encryption_context(inode)) { | ||||||
|  | 		if (!ext4_empty_dir(inode)) | ||||||
|  | 			return -ENOTEMPTY; | ||||||
|  | 		return ext4_create_encryption_context_from_policy(inode, | ||||||
|  | 								  policy); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (ext4_is_encryption_context_consistent_with_policy(inode, policy)) | ||||||
|  | 		return 0; | ||||||
|  | 
 | ||||||
|  | 	printk(KERN_WARNING "%s: Policy inconsistent with encryption context\n", | ||||||
|  | 	       __func__); | ||||||
|  | 	return -EINVAL; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int ext4_get_policy(struct inode *inode, struct ext4_encryption_policy *policy) | ||||||
|  | { | ||||||
|  | 	struct ext4_encryption_context ctx; | ||||||
|  | 
 | ||||||
|  | 	int res = ext4_xattr_get(inode, EXT4_XATTR_INDEX_ENCRYPTION, | ||||||
|  | 				 EXT4_XATTR_NAME_ENCRYPTION_CONTEXT, | ||||||
|  | 				 &ctx, sizeof(ctx)); | ||||||
|  | 	if (res != sizeof(ctx)) | ||||||
|  | 		return -ENOENT; | ||||||
|  | 	if (ctx.format != EXT4_ENCRYPTION_CONTEXT_FORMAT_V1) | ||||||
|  | 		return -EINVAL; | ||||||
|  | 	policy->version = 0; | ||||||
|  | 	policy->contents_encryption_mode = ctx.contents_encryption_mode; | ||||||
|  | 	policy->filenames_encryption_mode = ctx.filenames_encryption_mode; | ||||||
|  | 	memcpy(&policy->master_key_descriptor, ctx.master_key_descriptor, | ||||||
|  | 	       EXT4_KEY_DESCRIPTOR_SIZE); | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int ext4_is_child_context_consistent_with_parent(struct inode *parent, | ||||||
|  | 						 struct inode *child) | ||||||
|  | { | ||||||
|  | 	struct ext4_encryption_context parent_ctx, child_ctx; | ||||||
|  | 	int res; | ||||||
|  | 
 | ||||||
|  | 	if ((parent == NULL) || (child == NULL)) { | ||||||
|  | 		pr_err("parent %p child %p\n", parent, child); | ||||||
|  | 		BUG_ON(1); | ||||||
|  | 	} | ||||||
|  | 	/* no restrictions if the parent directory is not encrypted */ | ||||||
|  | 	if (!ext4_encrypted_inode(parent)) | ||||||
|  | 		return 1; | ||||||
|  | 	res = ext4_xattr_get(parent, EXT4_XATTR_INDEX_ENCRYPTION, | ||||||
|  | 			     EXT4_XATTR_NAME_ENCRYPTION_CONTEXT, | ||||||
|  | 			     &parent_ctx, sizeof(parent_ctx)); | ||||||
|  | 	if (res != sizeof(parent_ctx)) | ||||||
|  | 		return 0; | ||||||
|  | 	/* if the child directory is not encrypted, this is always a problem */ | ||||||
|  | 	if (!ext4_encrypted_inode(child)) | ||||||
|  | 		return 0; | ||||||
|  | 	res = ext4_xattr_get(child, EXT4_XATTR_INDEX_ENCRYPTION, | ||||||
|  | 			     EXT4_XATTR_NAME_ENCRYPTION_CONTEXT, | ||||||
|  | 			     &child_ctx, sizeof(child_ctx)); | ||||||
|  | 	if (res != sizeof(child_ctx)) | ||||||
|  | 		return 0; | ||||||
|  | 	return (memcmp(parent_ctx.master_key_descriptor, | ||||||
|  | 		       child_ctx.master_key_descriptor, | ||||||
|  | 		       EXT4_KEY_DESCRIPTOR_SIZE) == 0 && | ||||||
|  | 		(parent_ctx.contents_encryption_mode == | ||||||
|  | 		 child_ctx.contents_encryption_mode) && | ||||||
|  | 		(parent_ctx.filenames_encryption_mode == | ||||||
|  | 		 child_ctx.filenames_encryption_mode)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * ext4_inherit_context() - Sets a child context from its parent | ||||||
|  |  * @parent: Parent inode from which the context is inherited. | ||||||
|  |  * @child:  Child inode that inherits the context from @parent. | ||||||
|  |  * | ||||||
|  |  * Return: Zero on success, non-zero otherwise | ||||||
|  |  */ | ||||||
|  | int ext4_inherit_context(struct inode *parent, struct inode *child) | ||||||
|  | { | ||||||
|  | 	struct ext4_encryption_context ctx; | ||||||
|  | 	int res = ext4_xattr_get(parent, EXT4_XATTR_INDEX_ENCRYPTION, | ||||||
|  | 				 EXT4_XATTR_NAME_ENCRYPTION_CONTEXT, | ||||||
|  | 				 &ctx, sizeof(ctx)); | ||||||
|  | 
 | ||||||
|  | 	if (res != sizeof(ctx)) | ||||||
|  | 		return -ENOENT; | ||||||
|  | 
 | ||||||
|  | 	get_random_bytes(ctx.nonce, EXT4_KEY_DERIVATION_NONCE_SIZE); | ||||||
|  | 	res = ext4_xattr_set(child, EXT4_XATTR_INDEX_ENCRYPTION, | ||||||
|  | 			     EXT4_XATTR_NAME_ENCRYPTION_CONTEXT, &ctx, | ||||||
|  | 			     sizeof(ctx), 0); | ||||||
|  | 	if (!res) | ||||||
|  | 		ext4_set_inode_flag(child, EXT4_INODE_ENCRYPT); | ||||||
|  | 	return res; | ||||||
|  | } | ||||||
| @ -589,6 +589,8 @@ enum { | |||||||
| #define EXT4_ENCRYPTION_MODE_AES_256_CBC	3 | #define EXT4_ENCRYPTION_MODE_AES_256_CBC	3 | ||||||
| #define EXT4_ENCRYPTION_MODE_AES_256_CTS	4 | #define EXT4_ENCRYPTION_MODE_AES_256_CTS	4 | ||||||
| 
 | 
 | ||||||
|  | #include "ext4_crypto.h" | ||||||
|  | 
 | ||||||
| /*
 | /*
 | ||||||
|  * ioctl commands |  * ioctl commands | ||||||
|  */ |  */ | ||||||
| @ -610,6 +612,9 @@ enum { | |||||||
| #define EXT4_IOC_RESIZE_FS		_IOW('f', 16, __u64) | #define EXT4_IOC_RESIZE_FS		_IOW('f', 16, __u64) | ||||||
| #define EXT4_IOC_SWAP_BOOT		_IO('f', 17) | #define EXT4_IOC_SWAP_BOOT		_IO('f', 17) | ||||||
| #define EXT4_IOC_PRECACHE_EXTENTS	_IO('f', 18) | #define EXT4_IOC_PRECACHE_EXTENTS	_IO('f', 18) | ||||||
|  | #define EXT4_IOC_SET_ENCRYPTION_POLICY	_IOR('f', 19, struct ext4_encryption_policy) | ||||||
|  | #define EXT4_IOC_GET_ENCRYPTION_PWSALT	_IOW('f', 20, __u8[16]) | ||||||
|  | #define EXT4_IOC_GET_ENCRYPTION_POLICY	_IOW('f', 21, struct ext4_encryption_policy) | ||||||
| 
 | 
 | ||||||
| #if defined(__KERNEL__) && defined(CONFIG_COMPAT) | #if defined(__KERNEL__) && defined(CONFIG_COMPAT) | ||||||
| /*
 | /*
 | ||||||
| @ -2011,6 +2016,16 @@ extern unsigned ext4_free_clusters_after_init(struct super_block *sb, | |||||||
| 					      struct ext4_group_desc *gdp); | 					      struct ext4_group_desc *gdp); | ||||||
| ext4_fsblk_t ext4_inode_to_goal_block(struct inode *); | ext4_fsblk_t ext4_inode_to_goal_block(struct inode *); | ||||||
| 
 | 
 | ||||||
|  | /* crypto_policy.c */ | ||||||
|  | int ext4_is_child_context_consistent_with_parent(struct inode *parent, | ||||||
|  | 						 struct inode *child); | ||||||
|  | int ext4_inherit_context(struct inode *parent, struct inode *child); | ||||||
|  | void ext4_to_hex(char *dst, char *src, size_t src_size); | ||||||
|  | int ext4_process_policy(const struct ext4_encryption_policy *policy, | ||||||
|  | 			struct inode *inode); | ||||||
|  | int ext4_get_policy(struct inode *inode, | ||||||
|  | 		    struct ext4_encryption_policy *policy); | ||||||
|  | 
 | ||||||
| /* dir.c */ | /* dir.c */ | ||||||
| extern int __ext4_check_dir_entry(const char *, unsigned int, struct inode *, | extern int __ext4_check_dir_entry(const char *, unsigned int, struct inode *, | ||||||
| 				  struct file *, | 				  struct file *, | ||||||
|  | |||||||
							
								
								
									
										49
									
								
								fs/ext4/ext4_crypto.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								fs/ext4/ext4_crypto.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,49 @@ | |||||||
|  | /*
 | ||||||
|  |  * linux/fs/ext4/ext4_crypto.h | ||||||
|  |  * | ||||||
|  |  * Copyright (C) 2015, Google, Inc. | ||||||
|  |  * | ||||||
|  |  * This contains encryption header content for ext4 | ||||||
|  |  * | ||||||
|  |  * Written by Michael Halcrow, 2015. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #ifndef _EXT4_CRYPTO_H | ||||||
|  | #define _EXT4_CRYPTO_H | ||||||
|  | 
 | ||||||
|  | #include <linux/fs.h> | ||||||
|  | 
 | ||||||
|  | #define EXT4_KEY_DESCRIPTOR_SIZE 8 | ||||||
|  | 
 | ||||||
|  | /* Policy provided via an ioctl on the topmost directory */ | ||||||
|  | struct ext4_encryption_policy { | ||||||
|  | 	char version; | ||||||
|  | 	char contents_encryption_mode; | ||||||
|  | 	char filenames_encryption_mode; | ||||||
|  | 	char master_key_descriptor[EXT4_KEY_DESCRIPTOR_SIZE]; | ||||||
|  | } __attribute__((__packed__)); | ||||||
|  | 
 | ||||||
|  | #define EXT4_ENCRYPTION_CONTEXT_FORMAT_V1 1 | ||||||
|  | #define EXT4_KEY_DERIVATION_NONCE_SIZE 16 | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Encryption context for inode | ||||||
|  |  * | ||||||
|  |  * Protector format: | ||||||
|  |  *  1 byte: Protector format (1 = this version) | ||||||
|  |  *  1 byte: File contents encryption mode | ||||||
|  |  *  1 byte: File names encryption mode | ||||||
|  |  *  1 byte: Reserved | ||||||
|  |  *  8 bytes: Master Key descriptor | ||||||
|  |  *  16 bytes: Encryption Key derivation nonce | ||||||
|  |  */ | ||||||
|  | struct ext4_encryption_context { | ||||||
|  | 	char format; | ||||||
|  | 	char contents_encryption_mode; | ||||||
|  | 	char filenames_encryption_mode; | ||||||
|  | 	char reserved; | ||||||
|  | 	char master_key_descriptor[EXT4_KEY_DESCRIPTOR_SIZE]; | ||||||
|  | 	char nonce[EXT4_KEY_DERIVATION_NONCE_SIZE]; | ||||||
|  | } __attribute__((__packed__)); | ||||||
|  | 
 | ||||||
|  | #endif	/* _EXT4_CRYPTO_H */ | ||||||
| @ -13,6 +13,7 @@ | |||||||
| #include <linux/compat.h> | #include <linux/compat.h> | ||||||
| #include <linux/mount.h> | #include <linux/mount.h> | ||||||
| #include <linux/file.h> | #include <linux/file.h> | ||||||
|  | #include <linux/random.h> | ||||||
| #include <asm/uaccess.h> | #include <asm/uaccess.h> | ||||||
| #include "ext4_jbd2.h" | #include "ext4_jbd2.h" | ||||||
| #include "ext4.h" | #include "ext4.h" | ||||||
| @ -195,6 +196,16 @@ journal_err_out: | |||||||
| 	return err; | 	return err; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static int uuid_is_zero(__u8 u[16]) | ||||||
|  | { | ||||||
|  | 	int	i; | ||||||
|  | 
 | ||||||
|  | 	for (i = 0; i < 16; i++) | ||||||
|  | 		if (u[i]) | ||||||
|  | 			return 0; | ||||||
|  | 	return 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| long ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) | long ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) | ||||||
| { | { | ||||||
| 	struct inode *inode = file_inode(filp); | 	struct inode *inode = file_inode(filp); | ||||||
| @ -614,7 +625,78 @@ resizefs_out: | |||||||
| 	} | 	} | ||||||
| 	case EXT4_IOC_PRECACHE_EXTENTS: | 	case EXT4_IOC_PRECACHE_EXTENTS: | ||||||
| 		return ext4_ext_precache(inode); | 		return ext4_ext_precache(inode); | ||||||
|  | 	case EXT4_IOC_SET_ENCRYPTION_POLICY: { | ||||||
|  | #ifdef CONFIG_EXT4_FS_ENCRYPTION | ||||||
|  | 		struct ext4_encryption_policy policy; | ||||||
|  | 		int err = 0; | ||||||
| 
 | 
 | ||||||
|  | 		if (copy_from_user(&policy, | ||||||
|  | 				   (struct ext4_encryption_policy __user *)arg, | ||||||
|  | 				   sizeof(policy))) { | ||||||
|  | 			err = -EFAULT; | ||||||
|  | 			goto encryption_policy_out; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		err = ext4_process_policy(&policy, inode); | ||||||
|  | encryption_policy_out: | ||||||
|  | 		return err; | ||||||
|  | #else | ||||||
|  | 		return -EOPNOTSUPP; | ||||||
|  | #endif | ||||||
|  | 	} | ||||||
|  | 	case EXT4_IOC_GET_ENCRYPTION_PWSALT: { | ||||||
|  | 		int err, err2; | ||||||
|  | 		struct ext4_sb_info *sbi = EXT4_SB(sb); | ||||||
|  | 		handle_t *handle; | ||||||
|  | 
 | ||||||
|  | 		if (!ext4_sb_has_crypto(sb)) | ||||||
|  | 			return -EOPNOTSUPP; | ||||||
|  | 		if (uuid_is_zero(sbi->s_es->s_encrypt_pw_salt)) { | ||||||
|  | 			err = mnt_want_write_file(filp); | ||||||
|  | 			if (err) | ||||||
|  | 				return err; | ||||||
|  | 			handle = ext4_journal_start_sb(sb, EXT4_HT_MISC, 1); | ||||||
|  | 			if (IS_ERR(handle)) { | ||||||
|  | 				err = PTR_ERR(handle); | ||||||
|  | 				goto pwsalt_err_exit; | ||||||
|  | 			} | ||||||
|  | 			err = ext4_journal_get_write_access(handle, sbi->s_sbh); | ||||||
|  | 			if (err) | ||||||
|  | 				goto pwsalt_err_journal; | ||||||
|  | 			generate_random_uuid(sbi->s_es->s_encrypt_pw_salt); | ||||||
|  | 			err = ext4_handle_dirty_metadata(handle, NULL, | ||||||
|  | 							 sbi->s_sbh); | ||||||
|  | 		pwsalt_err_journal: | ||||||
|  | 			err2 = ext4_journal_stop(handle); | ||||||
|  | 			if (err2 && !err) | ||||||
|  | 				err = err2; | ||||||
|  | 		pwsalt_err_exit: | ||||||
|  | 			mnt_drop_write_file(filp); | ||||||
|  | 			if (err) | ||||||
|  | 				return err; | ||||||
|  | 		} | ||||||
|  | 		if (copy_to_user((void *) arg, sbi->s_es->s_encrypt_pw_salt, | ||||||
|  | 				 16)) | ||||||
|  | 			return -EFAULT; | ||||||
|  | 		return 0; | ||||||
|  | 	} | ||||||
|  | 	case EXT4_IOC_GET_ENCRYPTION_POLICY: { | ||||||
|  | #ifdef CONFIG_EXT4_FS_ENCRYPTION | ||||||
|  | 		struct ext4_encryption_policy policy; | ||||||
|  | 		int err = 0; | ||||||
|  | 
 | ||||||
|  | 		if (!ext4_encrypted_inode(inode)) | ||||||
|  | 			return -ENOENT; | ||||||
|  | 		err = ext4_get_policy(inode, &policy); | ||||||
|  | 		if (err) | ||||||
|  | 			return err; | ||||||
|  | 		if (copy_to_user((void *)arg, &policy, sizeof(policy))) | ||||||
|  | 			return -EFAULT; | ||||||
|  | 		return 0; | ||||||
|  | #else | ||||||
|  | 		return -EOPNOTSUPP; | ||||||
|  | #endif | ||||||
|  | 	} | ||||||
| 	default: | 	default: | ||||||
| 		return -ENOTTY; | 		return -ENOTTY; | ||||||
| 	} | 	} | ||||||
| @ -679,6 +761,9 @@ long ext4_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) | |||||||
| 	case FITRIM: | 	case FITRIM: | ||||||
| 	case EXT4_IOC_RESIZE_FS: | 	case EXT4_IOC_RESIZE_FS: | ||||||
| 	case EXT4_IOC_PRECACHE_EXTENTS: | 	case EXT4_IOC_PRECACHE_EXTENTS: | ||||||
|  | 	case EXT4_IOC_SET_ENCRYPTION_POLICY: | ||||||
|  | 	case EXT4_IOC_GET_ENCRYPTION_PWSALT: | ||||||
|  | 	case EXT4_IOC_GET_ENCRYPTION_POLICY: | ||||||
| 		break; | 		break; | ||||||
| 	default: | 	default: | ||||||
| 		return -ENOIOCTLCMD; | 		return -ENOIOCTLCMD; | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user