Intro

Since the only thing I can do is fixing random kernel bugs, I’d like to show how to understand and fix bugs reported by syzkaller by example. There are a lot of open bugs, so anyone who is looking for first contribution should try it! Most of the bugs really common, but in most cases maintainers do not have much time to fix bugs. It’s great chance to step in and join kernel community by just adding missing validation check or something

Configuration

To fix a bug in the Linux kernel you should at least download the sources. The official tree locates here and you can download it like this

$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git

Next step is configure git send-email. Linux kernel development process is based on plain text email. No github pull requests or something. Brutal old-school emails in plain text. Add following configuration into your ~/.gitconfig. Mine is for gmail accounts.

[sendemail]
	from = Name Surname <email@examle.com>
	smtpserver = smtp.gmail.com
	smtpuser = email@examle.com
	smtpencryption = tls
	smtppass = [pass]
	smtpserverport = 587
	confirm = auto

If you use 2 factor authentication smtppass field should be unique app password. You can create it on gmail security page.

Great! You have set up all needed environment.

Example of bug fixing

For this example I took this bug. It’s very simple, but it was open for 120 days!
Let’s take a look at the full report.

------------[ cut here ]------------
WARNING: CPU: 1 PID: 3603 at drivers/i2c/i2c-core-base.c:2178 __i2c_transfer+0xa14/0x17c0 drivers/i2c/i2c-core-base.c:2178 drivers/i2c/i2c-core-base.c:2178
Modules linked in:
CPU: 1 PID: 3603 Comm: syz-executor029 Not tainted 5.16.0-rc5-syzkaller #0
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 01/01/2011
RIP: 0010:__i2c_transfer+0xa14/0x17c0 drivers/i2c/i2c-core-base.c:2178 drivers/i2c/i2c-core-base.c:2178
Code: 0f 94 c7 31 ff 44 89 fe e8 e9 ab 9b fb 45 84 ff 0f 84 26 fd ff ff e8 fb a7 9b fb e8 65 3b 24 fb e9 17 fd ff ff e8 ec a7 9b fb <0f> 0b 41 bc ea ff ff ff e9 9e fd ff ff e8 da a7 9b fb 44 89 ee bf
RSP: 0018:ffffc90002aafce8 EFLAGS: 00010293
RAX: 0000000000000000 RBX: 0000000000000010 RCX: 0000000000000000
RDX: ffff88801c0f3a00 RSI: ffffffff85dc09e4 RDI: 0000000000000003
RBP: ffff88814a058b58 R08: 0000000000000000 R09: ffffffff8ff74ac7
R10: ffffffff85dc0008 R11: 0000000000000000 R12: 0000000000000010
R13: 0000000000000000 R14: ffff88814a058b78 R15: 0000000000000000
FS:  0000000000000000(0000) GS:ffff8880b9d00000(0063) knlGS:0000000056cc62c0
CS:  0010 DS: 002b ES: 002b CR0: 0000000080050033
CR2: 0000000020000000 CR3: 00000000773de000 CR4: 00000000003506e0
DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
Call Trace:
 <TASK>
 i2c_transfer+0x1e6/0x3e0 drivers/i2c/i2c-core-base.c:2269 drivers/i2c/i2c-core-base.c:2269
 i2cdev_ioctl_rdwr+0x583/0x6a0 drivers/i2c/i2c-dev.c:297 drivers/i2c/i2c-dev.c:297
 compat_i2cdev_ioctl+0x419/0x4f0 drivers/i2c/i2c-dev.c:561 drivers/i2c/i2c-dev.c:561
 __do_compat_sys_ioctl+0x1c7/0x290 fs/ioctl.c:972 fs/ioctl.c:972
 do_syscall_32_irqs_on arch/x86/entry/common.c:112 [inline]
 do_syscall_32_irqs_on arch/x86/entry/common.c:112 [inline] arch/x86/entry/common.c:178
 __do_fast_syscall_32+0x65/0xf0 arch/x86/entry/common.c:178 arch/x86/entry/common.c:178
 do_fast_syscall_32+0x2f/0x70 arch/x86/entry/common.c:203 arch/x86/entry/common.c:203
 entry_SYSENTER_compat_after_hwframe+0x4d/0x5c
RIP: 0023:0xf7e6e549
Code: 03 74 c0 01 10 05 03 74 b8 01 10 06 03 74 b4 01 10 07 03 74 b0 01 10 08 03 74 d8 01 00 00 00 00 00 51 52 55 89 e5 0f 34 cd 80 <5d> 5a 59 c3 90 90 90 90 8d b4 26 00 00 00 00 8d b4 26 00 00 00 00
RSP: 002b:00000000fffc8b7c EFLAGS: 00000246 ORIG_RAX: 0000000000000036
RAX: ffffffffffffffda RBX: 0000000000000003 RCX: 0000000000000707
RDX: 00000000200003c0 RSI: 00000000fffc8bd0 RDI: 00000000f7f15000
RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000000 R12: 0000000000000000
R13: 0000000000000000 R14: 0000000000000000 R15: 0000000000000000
 </TASK>
----------------
Code disassembly (best guess):
   0:	03 74 c0 01          	add    0x1(%rax,%rax,8),%esi
   4:	10 05 03 74 b8 01    	adc    %al,0x1b87403(%rip)        # 0x1b8740d
   a:	10 06                	adc    %al,(%rsi)
   c:	03 74 b4 01          	add    0x1(%rsp,%rsi,4),%esi
  10:	10 07                	adc    %al,(%rdi)
  12:	03 74 b0 01          	add    0x1(%rax,%rsi,4),%esi
  16:	10 08                	adc    %cl,(%rax)
  18:	03 74 d8 01          	add    0x1(%rax,%rbx,8),%esi
  1c:	00 00                	add    %al,(%rax)
  1e:	00 00                	add    %al,(%rax)
  20:	00 51 52             	add    %dl,0x52(%rcx)
  23:	55                   	push   %rbp
  24:	89 e5                	mov    %esp,%ebp
  26:	0f 34                	sysenter
  28:	cd 80                	int    $0x80
* 2a:	5d                   	pop    %rbp <-- trapping instruction
  2b:	5a                   	pop    %rdx
  2c:	59                   	pop    %rcx
  2d:	c3                   	retq
  2e:	90                   	nop
  2f:	90                   	nop
  30:	90                   	nop
  31:	90                   	nop
  32:	8d b4 26 00 00 00 00 	lea    0x0(%rsi,%riz,1),%esi
  39:	8d b4 26 00 00 00 00 	lea    0x0(%rsi,%riz,1),%esi
----------------
Code disassembly (best guess):
   0:	03 74 c0 01          	add    0x1(%rax,%rax,8),%esi
   4:	10 05 03 74 b8 01    	adc    %al,0x1b87403(%rip)        # 0x1b8740d
   a:	10 06                	adc    %al,(%rsi)
   c:	03 74 b4 01          	add    0x1(%rsp,%rsi,4),%esi
  10:	10 07                	adc    %al,(%rdi)
  12:	03 74 b0 01          	add    0x1(%rax,%rsi,4),%esi
  16:	10 08                	adc    %cl,(%rax)
  18:	03 74 d8 01          	add    0x1(%rax,%rbx,8),%esi
  1c:	00 00                	add    %al,(%rax)
  1e:	00 00                	add    %al,(%rax)
  20:	00 51 52             	add    %dl,0x52(%rcx)
  23:	55                   	push   %rbp
  24:	89 e5                	mov    %esp,%ebp
  26:	0f 34                	sysenter
  28:	cd 80                	int    $0x80
* 2a:	5d                   	pop    %rbp <-- trapping instruction
  2b:	5a                   	pop    %rdx
  2c:	59                   	pop    %rcx
  2d:	c3                   	retq
  2e:	90                   	nop
  2f:	90                   	nop
  30:	90                   	nop
  31:	90                   	nop
  32:	8d b4 26 00 00 00 00 	lea    0x0(%rsi,%riz,1),%esi
  39:	8d b4 26 00 00 00 00 	lea    0x0(%rsi,%riz,1),%esi

Report says there is a WARN_ON at drivers/i2c/i2c-core-base.c:2178. Let’s take a look what kind of warning it is.

	if (WARN_ON(!msgs || num < 1))
		return -EINVAL;

Hm, looks like somebody passed wrong arguments. Where these numbers come from? This question can be easily answered by looking at stack trace.

 i2c_transfer+0x1e6/0x3e0 drivers/i2c/i2c-core-base.c:2269 drivers/i2c/i2c-core-base.c:2269
 i2cdev_ioctl_rdwr+0x583/0x6a0 drivers/i2c/i2c-dev.c:297 drivers/i2c/i2c-dev.c:297
 compat_i2cdev_ioctl+0x419/0x4f0 drivers/i2c/i2c-dev.c:561 drivers/i2c/i2c-dev.c:561
 __do_compat_sys_ioctl+0x1c7/0x290 fs/ioctl.c:972 fs/ioctl.c:972
 do_syscall_32_irqs_on arch/x86/entry/common.c:112 [inline]
 do_syscall_32_irqs_on arch/x86/entry/common.c:112 [inline] arch/x86/entry/common.c:178
 __do_fast_syscall_32+0x65/0xf0 arch/x86/entry/common.c:178 arch/x86/entry/common.c:178
 do_fast_syscall_32+0x2f/0x70 arch/x86/entry/common.c:203 arch/x86/entry/common.c:203
 entry_SYSENTER_compat_after_hwframe+0x4d/0x5c

First 6 entries are common syscall kernel path, so we can simply ignore them. Next one is compat_i2cdev_ioctl. This function is ioctl handler for /dev/i2c-* devices. We are not interested in purpose of this devices. Something related to i2c. What we really interested in is what this function does – it copies some data from user-space and pass it deeper into i2c stack.

519 static long compat_i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)                                                 
520 {                                
521         struct i2c_client *client = file->private_data;
522         unsigned long funcs;     
523         switch (cmd) {           
524         case I2C_FUNCS:          
525                 funcs = i2c_get_functionality(client->adapter);
526                 return put_user(funcs, (compat_ulong_t __user *)arg);
527         case I2C_RDWR: {         
528                 struct i2c_rdwr_ioctl_data32 rdwr_arg;
529                 struct i2c_msg32 __user *p;
530                 struct i2c_msg *rdwr_pa;
531                 int i;           
532                                  
533                 if (copy_from_user(&rdwr_arg,
534                                    (struct i2c_rdwr_ioctl_data32 __user *)arg,
535                                    sizeof(rdwr_arg)))
536                         return -EFAULT;
537                                  
538                 if (rdwr_arg.nmsgs > I2C_RDWR_IOCTL_MAX_MSGS)
539                         return -EINVAL;
540                                  
541                 rdwr_pa = kmalloc_array(rdwr_arg.nmsgs, sizeof(struct i2c_msg),
542                                       GFP_KERNEL);
543                 if (!rdwr_pa)    
544                         return -ENOMEM;
545                                  
546                 p = compat_ptr(rdwr_arg.msgs);
547                 for (i = 0; i < rdwr_arg.nmsgs; i++) {
548                         struct i2c_msg32 umsg;
549                         if (copy_from_user(&umsg, p + i, sizeof(umsg))) {
550                                 kfree(rdwr_pa); 
551                                 return -EFAULT;
552                         }        
553                         rdwr_pa[i] = (struct i2c_msg) {
554                                 .addr = umsg.addr,
555                                 .flags = umsg.flags,
556                                 .len = umsg.len,
557                                 .buf = compat_ptr(umsg.buf)
558                         };       
559                 }                
560                                  
561                 return i2cdev_ioctl_rdwr(client, rdwr_arg.nmsgs, rdwr_pa);
562         }                        
563         case I2C_SMBUS: {        
564                 struct i2c_smbus_ioctl_data32   data32;
565                 if (copy_from_user(&data32,
566                                    (void __user *) arg,
567                                    sizeof(data32)))
568                         return -EFAULT;
569                 return i2cdev_ioctl_smbus(client, data32.read_write,
570                                           data32.command,
571                                           data32.size,
572                                           compat_ptr(data32.data));
573         }                        
574         default:                 
575                 return i2cdev_ioctl(file, cmd, arg);
576         }                        
577 }

Next stack trace entry is i2cdev_ioctl_rdwr. From just looking at the code we can say, that reproducer passed I2C_RDWR as cmd. Take a closer look at the call

return i2cdev_ioctl_rdwr(client, rdwr_arg.nmsgs, rdwr_pa);

rdwr_arg.nmsgs is user controlled value. It’s obvious from copy_from_user call on line 533. Ok, let’s go down to stack. What does i2cdev_ioctl_rdwr? I won’t copy-paste full function, I will cut it to very simplified form


static noinline int i2cdev_ioctl_rdwr(struct i2c_client *client,
		unsigned nmsgs, struct i2c_msg *msgs)
{
	u8 __user **data_ptrs;
	int i, res;

	data_ptrs = kmalloc_array(nmsgs, sizeof(u8 __user *), GFP_KERNEL);
	if (data_ptrs == NULL) {
		kfree(msgs);
		return -ENOMEM;
	}

	res = 0;
	for (i = 0; i < nmsgs; i++) {
		/* something */
	}


	res = i2c_transfer(client->adapter, msgs, nmsgs); <--- Hm???

	/* something else */
}

Do you see a problem? Let’s go back to root case of the bug. There is following warning in i2c_transfer.

	if (WARN_ON(!msgs || num < 1))
		return -EINVAL;

And as we discovered above msgs is user-controlled value. Code must validate passed arguments before passing them down to i2c transfer functions. compat_i2cdev_ioctl check only for nmsgs max value, but not for nmsgs being zero.

I come up with this solution

diff --git a/drivers/i2c/i2c-dev.c b/drivers/i2c/i2c-dev.c
index bce0e8bb7852..cf5d049342ea 100644
--- a/drivers/i2c/i2c-dev.c
+++ b/drivers/i2c/i2c-dev.c
@@ -535,6 +535,9 @@ static long compat_i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned lo
                                   sizeof(rdwr_arg)))
                        return -EFAULT;
 
+               if (!rdwr_arg.msgs || rdwr_arg.nmsgs == 0)
+                       return -EINVAL;
+
                if (rdwr_arg.nmsgs > I2C_RDWR_IOCTL_MAX_MSGS)
                        return -EINVAL;
 

How to test your fix

The easiest way is delegate testing to syzbot. Full communication guide can be found here, but we need only one command.

#syz test: <tree> <branch or commit SHA>

For upstream bug use

#syz test git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git master

Just send an email to mail address that can be found on bug page

And attach your patch to the email (as file or as plain text). Syzbot will build the kernel and run reproducer for some time. If it won’t hit any bugs you will receive Reported-and-tested-by tag.

A bit harder way is to use qemu. You will need to build the kernel by yourself and it’s topic for separate post.

How to format send your patch

Commit your changes using

$ git add -u
$ git commit -s

-s is important! This argument automatically adds Signed-off-by with your email and name to commit log. Patches w/o Signed-off-by tag will be automatically rejected. It doesn’t matter if change is correct or not. 1st line is the subject of your patch. The format is following:

<subsystem name>: [<subsystem part if present>:] <short description of the change>

For this example:

i2c: validate user data in compat ioctl

The bug itself is in i2c core, so there is no need in specifying subsystem part. Subject line should be less than 75 characters in length, but it’s not strict rule. The main rule is – subject must be small and should describe the change. Put blank line after the subject line and describe the change in more details. Add why this change is needed, what’s the root case and how you have fixed it. You can add anything you feel related to the patch. Put blank line after commit message and before tags. It makes patches look more nice. Do not forget to add Reported-by tag from bug page or Reported-and-tested-by received from syzbot

Example patch message

    i2c: validate user data in compat ioctl	<- Subject line
						<- Blank line before commit message
    Wrong user data may cause warning in i2c_transfer(), ex: zero msgs.
    Userspace should not be able to trigger warnings, so this patch adds
    validation checks for user data in compact ioctl to prevent reported
    warnings
						<- Blank line before tags
    Reported-and-tested-by: syzbot+e417648b303855b91d8a@syzkaller.appspotmail.com
    Fixes: 7d5cb45655f2 ("i2c compat ioctls: move to ->compat_ioctl()")
    Signed-off-by: Pavel Skripkin <paskripkin@gmail.com>

FWIW, commit message itself should be formatted to 75 chars per line. Some maintainers might get mad at you for wrong formatting. The full guide for submitting patches can be found here.

Ok, your patch is formatted in your local tree. How to post it to mailing lists? First of all use

$ git format-patch HEAD~
0001-i2c-validate-user-data-in-compat-ioctl.patch
$ 

command. It will create formatted patch email which you can send using git send-email command. Do not hurry with sending it. Smart kernel developers created a script for finding common mistakes in patches. Do not forget to use it before posting a patch. Simply run this command from kernel root directory

$ ./scripts/checkpatch.pl 0001-i2c-validate-user-data-in-compat-ioctl.patch
WARNING: Possible unwrapped commit description (prefer a maximum 75 chars per line)
#11: 
Reported-and-tested-by: syzbot+e417648b303855b91d8a@syzkaller.appspotmail.com

total: 0 errors, 1 warnings, 9 lines checked

NOTE: For some of the reported defects, checkpatch may be able to
      mechanically convert to the typical style using --fix or --fix-inplace.

0001-i2c-validate-user-data-in-compat-ioctl.patch has style problems, please review.

NOTE: If any of the errors are false positives, please report
      them to the maintainer, see CHECKPATCH in MAINTAINERS.

There is one warning. It can be ignored, because tags must be one-liners. So looks like our patch is ready for submittion. The last thing we need the list of people interested in our change. There is also a script for that. It’s called get_maintainer.pl. Example of usage:

$ ./scripts/get_maintainer.pl 0001-i2c-validate-user-data-in-compat-ioctl.patch 
Wolfram Sang <wsa@kernel.org> (maintainer:I2C SUBSYSTEM)
Al Viro <viro@zeniv.linux.org.uk> (blamed_fixes:1/1=100%)
linux-i2c@vger.kernel.org (open list:I2C SUBSYSTEM)
linux-kernel@vger.kernel.org (open list)

Not so many people… And the last step is send the patch. Just put all needed emails in git send-email arguments and press enter. Like this

$ git send-email \
> --to=wsa@kernel.org \
> --to=viro@zeniv.linux.org.uk \
> --cc=linux-i2c@vger.kernel.org \
> --cc=linux-kernel@vger.kernel.org \
> 0001-i2c-validate-user-data-in-compat-ioctl.patch

You can check https://lore.kernel.org/ to see if patch reached the lists. Mine can be found here. If your patch reached the lists then you just need to sit down and relax. Sometimes maintainers reply within a day, but average waiting time for a feedback is 7-8 days. Don’t hurry, maintainers are very busy people, so no need to rush.

Good luck in bug fixing and thanks for reading!