Home | History | Annotate | Download | only in dirtyc0w
      1 /*
      2  * Copyright (c) 2016 Cyril Hrubis <chrubis (at) suse.cz>
      3  *
      4  * This program is free software: you can redistribute it and/or modify
      5  * it under the terms of the GNU General Public License as published by
      6  * the Free Software Foundation, either version 2 of the License, or
      7  * (at your option) any later version.
      8  *
      9  * This program is distributed in the hope that it will be useful,
     10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12  * GNU General Public License for more details.
     13  *
     14  * You should have received a copy of the GNU General Public License
     15  * along with this program. If not, see <http://www.gnu.org/licenses/>.
     16  */
     17 
     18 /*
     19  * This is a regression test for write race that allows unprivileged programs
     20  * to change readonly files on the system.
     21  *
     22  * It has been fixed long time ago:
     23  *
     24  *   commit 4ceb5db9757aaeadcf8fbbf97d76bd42aa4df0d6
     25  *   Author: Linus Torvalds <torvalds (at) g5.osdl.org>
     26  *   Date:   Mon Aug 1 11:14:49 2005 -0700
     27  *
     28  *   Fix get_user_pages() race for write access
     29  *
     30  * Then it reappeared and was fixed again in:
     31  *
     32  *   commit 19be0eaffa3ac7d8eb6784ad9bdbc7d67ed8e619
     33  *   Author: Linus Torvalds <torvalds (at) linux-foundation.org>
     34  *   Date:   Thu Oct 13 20:07:36 2016 GMT
     35  *
     36  *   mm: remove gup_flags FOLL_WRITE games from __get_user_pages()
     37  */
     38 
     39 #include <sys/mman.h>
     40 #include <fcntl.h>
     41 #include <pthread.h>
     42 #include <unistd.h>
     43 #include <sys/stat.h>
     44 #include <string.h>
     45 #include <stdlib.h>
     46 #include <pwd.h>
     47 
     48 #include "tst_test.h"
     49 
     50 #define FNAME "test"
     51 #define STR   "this is not a test\n"
     52 
     53 static uid_t nobody_uid;
     54 static gid_t nobody_gid;
     55 
     56 static void setup(void)
     57 {
     58 	struct passwd *pw;
     59 
     60 	pw = SAFE_GETPWNAM("nobody");
     61 
     62 	nobody_uid = pw->pw_uid;
     63 	nobody_gid = pw->pw_gid;
     64 }
     65 
     66 void dirtyc0w_test(void)
     67 {
     68 	int i, fd, pid, fail = 0;
     69 	char c;
     70 
     71 	/* Create file */
     72 	fd = SAFE_OPEN(FNAME, O_WRONLY|O_CREAT|O_EXCL, 0444);
     73 	SAFE_WRITE(1, fd, STR, sizeof(STR)-1);
     74 	SAFE_CLOSE(fd);
     75 
     76 	pid = SAFE_FORK();
     77 
     78 	if (!pid) {
     79 		SAFE_SETGID(nobody_gid);
     80 		SAFE_SETUID(nobody_uid);
     81 		SAFE_EXECLP("dirtyc0w_child", "dirtyc0w_child", NULL);
     82 	}
     83 
     84 	TST_CHECKPOINT_WAIT(0);
     85 	for (i = 0; i < 100; i++)  {
     86 		usleep(10000);
     87 
     88 		SAFE_FILE_SCANF(FNAME, "%c", &c);
     89 
     90 		if (c != 't') {
     91 			fail = 1;
     92 			break;
     93 		}
     94 	}
     95 
     96 	SAFE_KILL(pid, SIGUSR1);
     97 	tst_reap_children();
     98 	SAFE_UNLINK(FNAME);
     99 
    100 	if (fail)
    101 		tst_res(TFAIL, "Bug reproduced!");
    102 	else
    103 		tst_res(TPASS, "Bug not reproduced");
    104 }
    105 
    106 static struct tst_test test = {
    107 	.needs_tmpdir = 1,
    108 	.needs_checkpoints = 1,
    109 	.forks_child = 1,
    110 	.needs_root = 1,
    111 	.setup = setup,
    112 	.test_all = dirtyc0w_test,
    113 };
    114