SELinux: per-command whitelisting of ioctls

 note that this patch depends on a prior patch that is already in
 android-3.4 but has not apparently found its way into the msm 3.4
 branches (but is included in exynos and tegra),
 https://android-review.googlesource.com/#/c/92962/

Extend the generic ioctl permission check with support for per-command
filtering. Source/target/class sets including the ioctl permission may
additionally include a set of commands. Example:

allow <source> <target>:<class> { 0x8910-0x8926 0x892A-0x8935 }
auditallow <source> <target>:<class> 0x892A

When ioctl commands are omitted only the permissions are checked. This
feature is intended to provide finer granularity for the ioctl
permission which may be too imprecise in some circumstances. For
example, the same driver may use ioctls to provide important and
benign functionality such as driver version or socket type as well as
dangerous capabilities such as debugging features, read/write/execute
to physical memory or access to sensitive data. Per-command filtering
provides a mechanism to reduce the attack surface of the kernel, and
limit applications to the subset of commands required.

The format of the policy binary has been modified to include ioctl
commands, and the policy version number has been incremented to
POLICYDB_VERSION_IOCTL_OPERATIONS=30 to account for the format change.

Bug: 20350607
Bug: 18087110
Change-Id: Ibf0e36728f6f3f0d5af56ccdeddee40800af689d
Signed-off-by: Jeff Vander Stoep <jeffv@google.com>
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 17fdc17..9d204db 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -3096,6 +3096,46 @@
 	file_free_security(file);
 }
 
+/*
+ * Check whether a task has the ioctl permission and cmd
+ * operation to an inode.
+ */
+int ioctl_has_perm(const struct cred *cred, struct file *file,
+		u32 requested, u16 cmd)
+{
+	struct common_audit_data ad;
+	struct file_security_struct *fsec = file->f_security;
+	struct inode *inode = file->f_path.dentry->d_inode;
+	struct inode_security_struct *isec = inode->i_security;
+	struct lsm_ioctlop_audit ioctl;
+	u32 ssid = cred_sid(cred);
+	struct selinux_audit_data sad = {0,};
+	int rc;
+
+	COMMON_AUDIT_DATA_INIT(&ad, IOCTL_OP);
+	ad.u.op = &ioctl;
+	ad.u.op->cmd = cmd;
+	ad.u.op->path = file->f_path;
+	ad.selinux_audit_data = &sad;
+
+	if (ssid != fsec->sid) {
+		rc = avc_has_perm(ssid, fsec->sid,
+				SECCLASS_FD,
+				FD__USE,
+				&ad);
+		if (rc)
+			goto out;
+	}
+
+	if (unlikely(IS_PRIVATE(inode)))
+		return 0;
+
+	rc = avc_has_operation(ssid, isec->sid, isec->sclass,
+			requested, cmd, &ad);
+out:
+	return rc;
+}
+
 static int selinux_file_ioctl(struct file *file, unsigned int cmd,
 			      unsigned long arg)
 {
@@ -3138,7 +3178,7 @@
 	 * to the file's ioctl() function.
 	 */
 	default:
-		error = file_has_perm(cred, file, FILE__IOCTL);
+		error = ioctl_has_perm(cred, file, FILE__IOCTL, (u16) cmd);
 	}
 	return error;
 }