Vulnerability: In the Linux kernel before 5.4.12, drivers/input/input.c has out-of-bounds writes via a crafted keycode table, as demonstrated by input_set_keycode, aka CID-cb222aed03d7.
Vulnerability ID: CVE-2019-20636
CWE: CWE-787
Severity score: 10
Project: https://github.com/torvalds/linux
Published: 2020-04-08
File: drivers/input/input.c
Commit message: Input: add safety guards to input_set_keycode()

If we happen to have a garbage in input device's keycode table with values
too big we'll end up doing clear_bit() with offset way outside of our
bitmaps, damaging other objects within an input device or even outside of
it. Let's add sanity checks to the returned old keycodes.

Reported-by: syzbot+c769968809f9359b07aa@syzkaller.appspotmail.com
Reported-by: syzbot+76f3a30e88d256644c78@syzkaller.appspotmail.com
Link: https://lore.kernel.org/r/20191207212757.GA245964@dtor-ws
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Commit author: Dmitry Torokhov

Vulnerable version (before fix):
 static int input_default_setkeycode(struct input_dev *dev, 		} 	}  	__clear_bit(*old_keycode, dev->keybit); 	__set_bit(ke->keycode, dev->keybit);  	for (i = 0; i < dev->keycodemax; i++) { 		if (input_fetch_keycode(dev, i) == *old_keycode) { 			__set_bit(*old_keycode, dev->keybit); 			break; /* Setting the bit twice is useless, so break */ 		} 	}  	return 0; }  int input_set_keycode(struct input_dev *dev, 	 * Simulate keyup event if keycode is not present 	 * in the keymap anymore 	 */ 	if (test_bit(EV_KEY, dev->evbit) && 	    !is_event_supported(old_keycode, dev->keybit, KEY_MAX) && 	    __test_and_clear_bit(old_keycode, dev->key)) { 		struct input_value vals[] =  { 			{ EV_KEY, old_keycode, 0 }, 			input_value_sync

Fixed version (after fix):
 static int input_default_setkeycode(struct input_dev *dev, 		} 	}  	if (*old_keycode <= KEY_MAX) { 		__clear_bit(*old_keycode, dev->keybit); 		for (i = 0; i < dev->keycodemax; i++) { 			if (input_fetch_keycode(dev, i) == *old_keycode) { 				__set_bit(*old_keycode, dev->keybit); 				/* Setting the bit twice is useless, so break */ 				break; 			} 		} 	}  	__set_bit(ke->keycode, dev->keybit); 	return 0; }  int input_set_keycode(struct input_dev *dev, 	 * Simulate keyup event if keycode is not present 	 * in the keymap anymore 	 */ 	if (old_keycode > KEY_MAX) { 		dev_warn(dev->dev.parent ?: &dev->dev, 			 "%s: got too big old keycode %#x\n", 			 __func__, old_keycode); 	} else if (test_bit(EV_KEY, dev->evbit) && 		   !is_event_supported(old_keycode, dev->keybit, KEY_MAX) && 		   __test_and_clear_bit(old_keycode, dev->key)) { 		struct input_value vals[] =  { 			{ EV_KEY, old_keycode, 0 }, 			input_value_sync

Patch:
patch: @@ -878,16 +878,18 @@ static int input_default_setkeycode(struct input_dev *dev,
 		}
 	}
 
-	__clear_bit(*old_keycode, dev->keybit);
-	__set_bit(ke->keycode, dev->keybit);
-
-	for (i = 0; i < dev->keycodemax; i++) {
-		if (input_fetch_keycode(dev, i) == *old_keycode) {
-			__set_bit(*old_keycode, dev->keybit);
-			break; /* Setting the bit twice is useless, so break */
+	if (*old_keycode <= KEY_MAX) {
+		__clear_bit(*old_keycode, dev->keybit);
+		for (i = 0; i < dev->keycodemax; i++) {
+			if (input_fetch_keycode(dev, i) == *old_keycode) {
+				__set_bit(*old_keycode, dev->keybit);
+				/* Setting the bit twice is useless, so break */
+				break;
+			}
 		}
 	}
 
+	__set_bit(ke->keycode, dev->keybit);
 	return 0;
 }
 
@@ -943,9 +945,13 @@ int input_set_keycode(struct input_dev *dev,
 	 * Simulate keyup event if keycode is not present
 	 * in the keymap anymore
 	 */
-	if (test_bit(EV_KEY, dev->evbit) &&
-	    !is_event_supported(old_keycode, dev->keybit, KEY_MAX) &&
-	    __test_and_clear_bit(old_keycode, dev->key)) {
+	if (old_keycode > KEY_MAX) {
+		dev_warn(dev->dev.parent ?: &dev->dev,
+			 "%s: got too big old keycode %#x\n",
+			 __func__, old_keycode);
+	} else if (test_bit(EV_KEY, dev->evbit) &&
+		   !is_event_supported(old_keycode, dev->keybit, KEY_MAX) &&
+		   __test_and_clear_bit(old_keycode, dev->key)) {
 		struct input_value vals[] =  {
 			{ EV_KEY, old_keycode, 0 },
 			input_value_sync
path: drivers/input/input.c