Skip to contentSkip to conversation

fault-inject: improve fail-nth interface

PATCH In review Akinobu Mita 3 participants 13 messages Apr 6, 2017 – Jul 13, 2017

Subsystems (4)
lore
Share & tools
lore

Akinobu Mita

author maintainer
#0 lore raw

cover fault-inject: improve fail-nth interface

This series tries to improve fail-nth interface which was added to -mm tree recently.

Akinobu Mita (5):
fault-inject: automatically detect the number base for fail-nth write
interface
fault-inject: parse as natural 1-based value for fail-nth write
interface
fault-inject: make fail-nth read/write interface symmetric
fault-inject: simplify access check for fail-nth
fault-inject: add /proc/<pid>/fail-nth

 Documentation/fault-injection/fault-injection.txt | 21 ++++++------
 fs/proc/base.c                                    | 40 +++++++++--------------
 include/linux/sched.h                             |  2 +-
 3 files changed, 28 insertions(+), 35 deletions(-)
Cc Dmitry Vyukov <[email protected]>
Signature · 1 line
2.7.4
author maintainer
#1 lore raw

PATCH 1/5 fault-inject: automatically detect the number base for fail-nth write interface

Automatically detect the number base to use when writing to fail-nth file instead of always parsing as a decimal number.

---
 fs/proc/base.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
fs/proc/base.c +1 -1
index f3fc74c..c85376b 100644
@@ -1371,7 +1371,7 @@ static ssize_t proc_fail_nth_write(struct file *file, const char __user *buf,
 	put_task_struct(task);
 	if (task != current)
 		return -EPERM;
-	err = kstrtoint_from_user(buf, count, 10, &n);
+	err = kstrtoint_from_user(buf, count, 0, &n);
 	if (err)
 		return err;
 	if (n < 0 || n == INT_MAX)
Signature · 1 line
2.7.4
author maintainer
#2 lore raw

PATCH 2/5 fault-inject: parse as natural 1-based value for fail-nth write interface

In reply to Akinobu Mita

The value written to fail-nth file is parsed as 0-based. Parsing as one-based is more natural to understand and it enables to cancel the previous setup by simply writing '0'.

This change also convert task->fail_nth from signed to unsigned int.

---
 Documentation/fault-injection/fault-injection.txt | 7 +++----
 fs/proc/base.c                                    | 9 ++++-----
 include/linux/sched.h                             | 2 +-
 3 files changed, 8 insertions(+), 10 deletions(-)
Documentation/fault-injection/fault-injection.txt +3 -4
index 192d8cb..a321905 100644
@@ -138,8 +138,8 @@ o proc entries
 
 - /proc/self/task/<current-tid>/fail-nth:
 
-	Write to this file of integer N makes N-th call in the current task fail
-	(N is 0-based). Read from this file returns a single char 'Y' or 'N'
+	Write to this file of integer N makes N-th call in the task fail.
+	Read from this file returns a single char 'Y' or 'N'
 	that says if the fault setup with a previous write to this file was
 	injected or not, and disables the fault if it wasn't yet injected.
 	Note that this file enables all types of faults (slab, futex, etc).
@@ -320,7 +320,7 @@ int main()
 	system("echo N > /sys/kernel/debug/failslab/ignore-gfp-wait");
 	sprintf(buf, "/proc/self/task/%ld/fail-nth", syscall(SYS_gettid));
 	fail_nth = open(buf, O_RDWR);
-	for (i = 0;; i++) {
+	for (i = 1;; i++) {
 		sprintf(buf, "%d", i);
 		write(fail_nth, buf, strlen(buf));
 		res = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds);
@@ -339,7 +339,6 @@ int main()
 
 An example output:
 
-0-th fault Y: res=-1/23
 1-th fault Y: res=-1/23
 2-th fault Y: res=-1/23
 3-th fault Y: res=-1/12
fs/proc/base.c +4 -5
index c85376b..42c52e2 100644
@@ -1363,7 +1363,8 @@ static ssize_t proc_fail_nth_write(struct file *file, const char __user *buf,
 				   size_t count, loff_t *ppos)
 {
 	struct task_struct *task;
-	int err, n;
+	int err;
+	unsigned int n;
 
 	task = get_proc_task(file_inode(file));
 	if (!task)
@@ -1371,12 +1372,10 @@ static ssize_t proc_fail_nth_write(struct file *file, const char __user *buf,
 	put_task_struct(task);
 	if (task != current)
 		return -EPERM;
-	err = kstrtoint_from_user(buf, count, 0, &n);
+	err = kstrtouint_from_user(buf, count, 0, &n);
 	if (err)
 		return err;
-	if (n < 0 || n == INT_MAX)
-		return -EINVAL;
-	current->fail_nth = n + 1;
+	current->fail_nth = n;
 	return count;
 }
 
include/linux/sched.h +1 -1
index f0f0854..bec5d97 100644
@@ -945,7 +945,7 @@ struct task_struct {
 
 #ifdef CONFIG_FAULT_INJECTION
 	int				make_it_fail;
-	int fail_nth;
+	unsigned int			fail_nth;
 #endif
 	/*
 	 * When (nr_dirtied >= nr_dirtied_pause), it's time to call
Signature · 1 line
2.7.4
author maintainer
#3 lore raw

PATCH 3/5 fault-inject: make fail-nth read/write interface symmetric

In reply to Akinobu Mita

The read interface for fail-nth looks a bit odd. Read from this file returns "NYYYY..." or "YYYYY..." (this makes me surprise when cat this file). Because there is no EOF condition. The first character indicates current->fail_nth is zero or not, and then current->fail_nth is reset to zero.

Just returning task->fail_nth value is more natural to understand.

---
 Documentation/fault-injection/fault-injection.txt | 13 +++++++------
 fs/proc/base.c                                    | 14 ++++++--------
 2 files changed, 13 insertions(+), 14 deletions(-)
Documentation/fault-injection/fault-injection.txt +7 -6
index a321905..370ddcb 100644
@@ -139,9 +139,9 @@ o proc entries
 - /proc/self/task/<current-tid>/fail-nth:
 
 	Write to this file of integer N makes N-th call in the task fail.
-	Read from this file returns a single char 'Y' or 'N'
-	that says if the fault setup with a previous write to this file was
-	injected or not, and disables the fault if it wasn't yet injected.
+	Read from this file returns a integer value. A value of '0' indicates
+	that the fault setup with a previous write to this file was injected.
+	A positive integer N indicates that the fault wasn't yet injected.
 	Note that this file enables all types of faults (slab, futex, etc).
 	This setting takes precedence over all other generic debugfs settings
 	like probability, interval, times, etc. But per-capability settings
@@ -325,13 +325,14 @@ int main()
 		write(fail_nth, buf, strlen(buf));
 		res = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds);
 		err = errno;
-		read(fail_nth, buf, 1);
+		pread(fail_nth, buf, sizeof(buf), 0);
 		if (res == 0) {
 			close(fds[0]);
 			close(fds[1]);
 		}
-		printf("%d-th fault %c: res=%d/%d\n", i, buf[0], res, err);
-		if (buf[0] != 'Y')
+		printf("%d-th fault %c: res=%d/%d\n", i, atoi(buf) ? 'N' : 'Y',
+			res, err);
+		if (atoi(buf))
 			break;
 	}
 	return 0;
fs/proc/base.c +6 -8
index 42c52e2..9d14215 100644
@@ -1383,7 +1383,8 @@ static ssize_t proc_fail_nth_read(struct file *file, char __user *buf,
 				  size_t count, loff_t *ppos)
 {
 	struct task_struct *task;
-	int err;
+	char numbuf[PROC_NUMBUF];
+	ssize_t len;
 
 	task = get_proc_task(file_inode(file));
 	if (!task)
@@ -1391,13 +1392,10 @@ static ssize_t proc_fail_nth_read(struct file *file, char __user *buf,
 	put_task_struct(task);
 	if (task != current)
 		return -EPERM;
-	if (count < 1)
-		return -EINVAL;
-	err = put_user((char)(current->fail_nth ? 'N' : 'Y'), buf);
-	if (err)
-		return err;
-	current->fail_nth = 0;
-	return 1;
+	len = snprintf(numbuf, sizeof(numbuf), "%u\n", task->fail_nth);
+	len = simple_read_from_buffer(buf, count, ppos, numbuf, len);
+
+	return len;
 }
 
 static const struct file_operations proc_fail_nth_operations = {
Signature · 1 line
2.7.4
#4 lore raw

Re: [PATCH -mm 3/5] fault-inject: make fail-nth read/write interface symmetric

Quoted 66 lines · Akinobu Mita

The read interface for fail-nth looks a bit odd. Read from this file returns "NYYYY..." or "YYYYY..." (this makes me surprise when cat this file). Because there is no EOF condition. The first character indicates current->fail_nth is zero or not, and then current->fail_nth is reset to zero.

Just returning task->fail_nth value is more natural to understand.

---
 Documentation/fault-injection/fault-injection.txt | 13 +++++++------
 fs/proc/base.c                                    | 14 ++++++--------
 2 files changed, 13 insertions(+), 14 deletions(-)
Documentation/fault-injection/fault-injection.txt +7 -6
index a321905..370ddcb 100644
@@ -139,9 +139,9 @@ o proc entries
 - /proc/self/task/<current-tid>/fail-nth:
 
        Write to this file of integer N makes N-th call in the task fail.
-       Read from this file returns a single char 'Y' or 'N'
-       that says if the fault setup with a previous write to this file was
-       injected or not, and disables the fault if it wasn't yet injected.
+       Read from this file returns a integer value. A value of '0' indicates
+       that the fault setup with a previous write to this file was injected.
+       A positive integer N indicates that the fault wasn't yet injected.
        Note that this file enables all types of faults (slab, futex, etc).
        This setting takes precedence over all other generic debugfs settings
        like probability, interval, times, etc. But per-capability settings
@@ -325,13 +325,14 @@ int main()
                write(fail_nth, buf, strlen(buf));
                res = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds);
                err = errno;
-               read(fail_nth, buf, 1);
+               pread(fail_nth, buf, sizeof(buf), 0);
                if (res == 0) {
                        close(fds[0]);
                        close(fds[1]);
                }
-               printf("%d-th fault %c: res=%d/%d\n", i, buf[0], res, err);
-               if (buf[0] != 'Y')
+               printf("%d-th fault %c: res=%d/%d\n", i, atoi(buf) ? 'N' : 'Y',
+                       res, err);
+               if (atoi(buf))
                        break;
        }
        return 0;
fs/proc/base.c +2 -1
index 42c52e2..9d14215 100644
@@ -1383,7 +1383,8 @@ static ssize_t proc_fail_nth_read(struct file *file, char __user *buf,
                                  size_t count, loff_t *ppos)
 {
        struct task_struct *task;
-       int err;
+       char numbuf[PROC_NUMBUF];
+       ssize_t len;
 
        task = get_proc_task(file_inode(file));
        if (!task)
@@ -1391,13 +1392,10 @@ static ssize_t proc_fail_nth_read(struct file *file, char __user *buf,
        put_task_struct(task);

fs/proc/base.c static ssize_t proc_fail_nth_read(struct file *file, char __user *buf, · line 1395

Quoted 10 lines
fs/proc/base.c +1 -7
        if (task != current)
                return -EPERM;
-       if (count < 1)
-               return -EINVAL;
-       err = put_user((char)(current->fail_nth ? 'N' : 'Y'), buf);
-       if (err)
-               return err;
-       current->fail_nth = 0;
-       return 1;
+       len = snprintf(numbuf, sizeof(numbuf), "%u\n", task->fail_nth);

If we allow setting this for non current task, then we need to prevent data races as the task uses task->fail_nth concurrently. Reads then should use READ_ONCE and writes in fault-inject.c should use WRITE_ONCE.

Quoted 9 lines
fs/proc/base.c +3 -1
+       len = simple_read_from_buffer(buf, count, ppos, numbuf, len);
+
+       return len;
 }
 
 static const struct file_operations proc_fail_nth_operations = {
--

2.7.4

#5 lore raw

Re: [PATCH -mm 3/5] fault-inject: make fail-nth read/write interface symmetric

Quoted 74 lines · On Fri, 7 Apr 2017 22:37:01 +0200 Dmitry Vyukov <[email protected]> wrote:
Quoted 73 lines · Akinobu Mita

The read interface for fail-nth looks a bit odd. Read from this file returns "NYYYY..." or "YYYYY..." (this makes me surprise when cat this file). Because there is no EOF condition. The first character indicates current->fail_nth is zero or not, and then current->fail_nth is reset to zero.

Just returning task->fail_nth value is more natural to understand.

---
 Documentation/fault-injection/fault-injection.txt | 13 +++++++------
 fs/proc/base.c                                    | 14 ++++++--------
 2 files changed, 13 insertions(+), 14 deletions(-)
Documentation/fault-injection/fault-injection.txt +7 -6
index a321905..370ddcb 100644
@@ -139,9 +139,9 @@ o proc entries
 - /proc/self/task/<current-tid>/fail-nth:
 
        Write to this file of integer N makes N-th call in the task fail.
-       Read from this file returns a single char 'Y' or 'N'
-       that says if the fault setup with a previous write to this file was
-       injected or not, and disables the fault if it wasn't yet injected.
+       Read from this file returns a integer value. A value of '0' indicates
+       that the fault setup with a previous write to this file was injected.
+       A positive integer N indicates that the fault wasn't yet injected.
        Note that this file enables all types of faults (slab, futex, etc).
        This setting takes precedence over all other generic debugfs settings
        like probability, interval, times, etc. But per-capability settings
@@ -325,13 +325,14 @@ int main()
                write(fail_nth, buf, strlen(buf));
                res = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds);
                err = errno;
-               read(fail_nth, buf, 1);
+               pread(fail_nth, buf, sizeof(buf), 0);
                if (res == 0) {
                        close(fds[0]);
                        close(fds[1]);
                }
-               printf("%d-th fault %c: res=%d/%d\n", i, buf[0], res, err);
-               if (buf[0] != 'Y')
+               printf("%d-th fault %c: res=%d/%d\n", i, atoi(buf) ? 'N' : 'Y',
+                       res, err);
+               if (atoi(buf))
                        break;
        }
        return 0;
fs/proc/base.c +2 -6
index 42c52e2..9d14215 100644
@@ -1383,7 +1383,8 @@ static ssize_t proc_fail_nth_read(struct file *file, char __user *buf,
                                  size_t count, loff_t *ppos)
 {
        struct task_struct *task;
-       int err;
+       char numbuf[PROC_NUMBUF];
+       ssize_t len;
 
        task = get_proc_task(file_inode(file));
        if (!task)
@@ -1391,13 +1392,10 @@ static ssize_t proc_fail_nth_read(struct file *file, char __user *buf,
        put_task_struct(task);
        if (task != current)
                return -EPERM;
-       if (count < 1)
-               return -EINVAL;
-       err = put_user((char)(current->fail_nth ? 'N' : 'Y'), buf);
-       if (err)
-               return err;
Quoted 8 lines

fs/proc/base.c static ssize_t proc_fail_nth_read(struct file *file, char __user *buf, · line 1395

Quoted 3 lines
fs/proc/base.c +1 -2
-       current->fail_nth = 0;
-       return 1;
+       len = snprintf(numbuf, sizeof(numbuf), "%u\n", task->fail_nth);

If we allow setting this for non current task, then we need to prevent data races as the task uses task->fail_nth concurrently. Reads then should use READ_ONCE and writes in fault-inject.c should use WRITE_ONCE.

This remains unresolved?

author maintainer
#6 lore raw

Re: [PATCH -mm 3/5] fault-inject: make fail-nth read/write interface symmetric

2017-07-13 5:49 GMT+09:00 Andrew Morton <[email protected]>:

Quoted 85 lines · On Fri, 7 Apr 2017 22:37:01 +0200 Dmitry Vyukov <[email protected]> wrote:
Quoted 75 lines · Akinobu Mita
Quoted 73 lines

The read interface for fail-nth looks a bit odd. Read from this file returns "NYYYY..." or "YYYYY..." (this makes me surprise when cat this file). Because there is no EOF condition. The first character indicates current->fail_nth is zero or not, and then current->fail_nth is reset to zero.

Just returning task->fail_nth value is more natural to understand.

---
 Documentation/fault-injection/fault-injection.txt | 13 +++++++------
 fs/proc/base.c                                    | 14 ++++++--------
 2 files changed, 13 insertions(+), 14 deletions(-)
Documentation/fault-injection/fault-injection.txt +7 -6
index a321905..370ddcb 100644
@@ -139,9 +139,9 @@ o proc entries
 - /proc/self/task/<current-tid>/fail-nth:
 
        Write to this file of integer N makes N-th call in the task fail.
-       Read from this file returns a single char 'Y' or 'N'
-       that says if the fault setup with a previous write to this file was
-       injected or not, and disables the fault if it wasn't yet injected.
+       Read from this file returns a integer value. A value of '0' indicates
+       that the fault setup with a previous write to this file was injected.
+       A positive integer N indicates that the fault wasn't yet injected.
        Note that this file enables all types of faults (slab, futex, etc).
        This setting takes precedence over all other generic debugfs settings
        like probability, interval, times, etc. But per-capability settings
@@ -325,13 +325,14 @@ int main()
                write(fail_nth, buf, strlen(buf));
                res = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds);
                err = errno;
-               read(fail_nth, buf, 1);
+               pread(fail_nth, buf, sizeof(buf), 0);
                if (res == 0) {
                        close(fds[0]);
                        close(fds[1]);
                }
-               printf("%d-th fault %c: res=%d/%d\n", i, buf[0], res, err);
-               if (buf[0] != 'Y')
+               printf("%d-th fault %c: res=%d/%d\n", i, atoi(buf) ? 'N' : 'Y',
+                       res, err);
+               if (atoi(buf))
                        break;
        }
        return 0;
fs/proc/base.c +2 -6
index 42c52e2..9d14215 100644
@@ -1383,7 +1383,8 @@ static ssize_t proc_fail_nth_read(struct file *file, char __user *buf,
                                  size_t count, loff_t *ppos)
 {
        struct task_struct *task;
-       int err;
+       char numbuf[PROC_NUMBUF];
+       ssize_t len;
 
        task = get_proc_task(file_inode(file));
        if (!task)
@@ -1391,13 +1392,10 @@ static ssize_t proc_fail_nth_read(struct file *file, char __user *buf,
        put_task_struct(task);
        if (task != current)
                return -EPERM;
-       if (count < 1)
-               return -EINVAL;
-       err = put_user((char)(current->fail_nth ? 'N' : 'Y'), buf);
-       if (err)
-               return err;
Quoted 8 lines

fs/proc/base.c static ssize_t proc_fail_nth_read(struct file *file, char __user *buf, · line 1395

Quoted 3 lines
fs/proc/base.c +1 -2
-       current->fail_nth = 0;
-       return 1;
+       len = snprintf(numbuf, sizeof(numbuf), "%u\n", task->fail_nth);

If we allow setting this for non current task, then we need to prevent data races as the task uses task->fail_nth concurrently. Reads then should use READ_ONCE and writes in fault-inject.c should use WRITE_ONCE.

Quoted 1 line

This remains unresolved?

I have just send a proposed fix. (Subject: [PATCH -mm] fault-inject: avoid unwanted data race to task->fail_nth)

author maintainer
#7 lore raw

PATCH 4/5 fault-inject: simplify access check for fail-nth

In reply to Akinobu Mita

The fail-nth file is created with 0666 and the access is permitted if and only if the task is current.

This file is owned by the currnet user. So we can create it with 0644 and allow the owner to write it. This enables to watch the status of task->fail_nth from another processes.

---
 fs/proc/base.c | 22 ++++++++--------------
 1 file changed, 8 insertions(+), 14 deletions(-)
fs/proc/base.c +8 -14
index 9d14215..14e7b73 100644
@@ -1366,16 +1366,16 @@ static ssize_t proc_fail_nth_write(struct file *file, const char __user *buf,
 	int err;
 	unsigned int n;
 
+	err = kstrtoint_from_user(buf, count, 0, &n);
+	if (err)
+		return err;
+
 	task = get_proc_task(file_inode(file));
 	if (!task)
 		return -ESRCH;
+	task->fail_nth = n;
 	put_task_struct(task);
-	if (task != current)
-		return -EPERM;
-	err = kstrtouint_from_user(buf, count, 0, &n);
-	if (err)
-		return err;
-	current->fail_nth = n;
+
 	return count;
 }
 
@@ -1389,11 +1389,9 @@ static ssize_t proc_fail_nth_read(struct file *file, char __user *buf,
 	task = get_proc_task(file_inode(file));
 	if (!task)
 		return -ESRCH;
-	put_task_struct(task);
-	if (task != current)
-		return -EPERM;
 	len = snprintf(numbuf, sizeof(numbuf), "%u\n", task->fail_nth);
 	len = simple_read_from_buffer(buf, count, ppos, numbuf, len);
+	put_task_struct(task);
 
 	return len;
 }
@@ -3358,11 +3356,7 @@ static const struct pid_entry tid_base_stuff[] = {
 #endif
 #ifdef CONFIG_FAULT_INJECTION
 	REG("make-it-fail", S_IRUGO|S_IWUSR, proc_fault_inject_operations),
-	/*
-	 * Operations on the file check that the task is current,
-	 * so we create it with 0666 to support testing under unprivileged user.
-	 */
-	REG("fail-nth", 0666, proc_fail_nth_operations),
+	REG("fail-nth", 0644, proc_fail_nth_operations),
 #endif
 #ifdef CONFIG_TASK_IO_ACCOUNTING
 	ONE("io",	S_IRUSR, proc_tid_io_accounting),
Signature · 1 line
2.7.4
#8 lore raw

Re: [PATCH -mm 4/5] fault-inject: simplify access check for fail-nth

Quoted 18 lines · Akinobu Mita

The fail-nth file is created with 0666 and the access is permitted if and only if the task is current.

This file is owned by the currnet user. So we can create it with 0644 and allow the owner to write it. This enables to watch the status of task->fail_nth from another processes.

---
 fs/proc/base.c | 22 ++++++++--------------
 1 file changed, 8 insertions(+), 14 deletions(-)
fs/proc/base.c +0 -0
index 9d14215..14e7b73 100644

fs/proc/base.c static ssize_t proc_fail_nth_write(struct file *file, const char __user *buf, · line 1369

Quoted 4 lines
fs/proc/base.c +1 -0
@@ -1366,16 +1366,16 @@ static ssize_t proc_fail_nth_write(struct file *file, const char __user *buf,
        int err;
        unsigned int n;
 
+       err = kstrtoint_from_user(buf, count, 0, &n);

/\/\/\/\/\

I think this should be kstrtouint_from_user. Otherwise it effective reverts one of previous patches.

Quoted 47 lines
fs/proc/base.c +7 -15
+       if (err)
+               return err;
+
        task = get_proc_task(file_inode(file));
        if (!task)
                return -ESRCH;
+       task->fail_nth = n;
        put_task_struct(task);
-       if (task != current)
-               return -EPERM;
-       err = kstrtouint_from_user(buf, count, 0, &n);
-       if (err)
-               return err;
-       current->fail_nth = n;
+
        return count;
 }
 
@@ -1389,11 +1389,9 @@ static ssize_t proc_fail_nth_read(struct file *file, char __user *buf,
        task = get_proc_task(file_inode(file));
        if (!task)
                return -ESRCH;
-       put_task_struct(task);
-       if (task != current)
-               return -EPERM;
        len = snprintf(numbuf, sizeof(numbuf), "%u\n", task->fail_nth);
        len = simple_read_from_buffer(buf, count, ppos, numbuf, len);
+       put_task_struct(task);
 
        return len;
 }
@@ -3358,11 +3356,7 @@ static const struct pid_entry tid_base_stuff[] = {
 #endif
 #ifdef CONFIG_FAULT_INJECTION
        REG("make-it-fail", S_IRUGO|S_IWUSR, proc_fault_inject_operations),
-       /*
-        * Operations on the file check that the task is current,
-        * so we create it with 0666 to support testing under unprivileged user.
-        */
-       REG("fail-nth", 0666, proc_fail_nth_operations),
+       REG("fail-nth", 0644, proc_fail_nth_operations),
 #endif
 #ifdef CONFIG_TASK_IO_ACCOUNTING
        ONE("io",       S_IRUSR, proc_tid_io_accounting),
--

2.7.4

#9 lore raw

Re: [PATCH -mm 4/5] fault-inject: simplify access check for fail-nth

In reply to Akinobu Mita
Quoted 54 lines · Akinobu Mita

The fail-nth file is created with 0666 and the access is permitted if and only if the task is current.

This file is owned by the currnet user. So we can create it with 0644 and allow the owner to write it. This enables to watch the status of task->fail_nth from another processes.

---
 fs/proc/base.c | 22 ++++++++--------------
 1 file changed, 8 insertions(+), 14 deletions(-)
fs/proc/base.c +7 -9
index 9d14215..14e7b73 100644
@@ -1366,16 +1366,16 @@ static ssize_t proc_fail_nth_write(struct file *file, const char __user *buf,
        int err;
        unsigned int n;
 
+       err = kstrtoint_from_user(buf, count, 0, &n);
+       if (err)
+               return err;
+
        task = get_proc_task(file_inode(file));
        if (!task)
                return -ESRCH;
+       task->fail_nth = n;
        put_task_struct(task);
-       if (task != current)
-               return -EPERM;
-       err = kstrtouint_from_user(buf, count, 0, &n);
-       if (err)
-               return err;
-       current->fail_nth = n;
+
        return count;
 }
 
@@ -1389,11 +1389,9 @@ static ssize_t proc_fail_nth_read(struct file *file, char __user *buf,
        task = get_proc_task(file_inode(file));
        if (!task)
                return -ESRCH;
-       put_task_struct(task);
-       if (task != current)
-               return -EPERM;
        len = snprintf(numbuf, sizeof(numbuf), "%u\n", task->fail_nth);
        len = simple_read_from_buffer(buf, count, ppos, numbuf, len);
+       put_task_struct(task);
 
        return len;
 }

fs/proc/base.c static const struct pid_entry tid_base_stuff[] = { · line 3359

Quoted 9 lines
fs/proc/base.c +1 -5
@@ -3358,11 +3356,7 @@ static const struct pid_entry tid_base_stuff[] = {
 #endif
 #ifdef CONFIG_FAULT_INJECTION
        REG("make-it-fail", S_IRUGO|S_IWUSR, proc_fault_inject_operations),
-       /*
-        * Operations on the file check that the task is current,
-        * so we create it with 0666 to support testing under unprivileged user.
-        */
-       REG("fail-nth", 0666, proc_fail_nth_operations),
+       REG("fail-nth", 0644, proc_fail_nth_operations),

/\/\/\/\/\/\

This breaks us.
Under setuid sandbox test threads can't open the file anymore. And we can't pre-open the files before dropping privs as new threads can be created afterwards.

I think the root cause of all these problems (permissions, parsing, serialization, broken cat, symmetry) is that we are trying to fit a programmatic API into reads and writes on textual files. We don't need symmetry, we don't need read+write to reset injection, we don't need parsing and serialization, it does not make sense to do this of non-current task, it definitely does not make sense to cat this, etc.

What do you think of 2 ioctls on /sys/kernel/debug/fail_nth?

Quoted 6 lines
fs/proc/base.c +0 -1
 #endif
 #ifdef CONFIG_TASK_IO_ACCOUNTING
        ONE("io",       S_IRUSR, proc_tid_io_accounting),
--

2.7.4

author maintainer
#10 lore raw

Re: [PATCH -mm 4/5] fault-inject: simplify access check for fail-nth

2017-04-08 5:45 GMT+09:00 Dmitry Vyukov <[email protected]>:

Quoted 67 lines · Akinobu Mita
Quoted 54 lines

The fail-nth file is created with 0666 and the access is permitted if and only if the task is current.

This file is owned by the currnet user. So we can create it with 0644 and allow the owner to write it. This enables to watch the status of task->fail_nth from another processes.

---
 fs/proc/base.c | 22 ++++++++--------------
 1 file changed, 8 insertions(+), 14 deletions(-)
fs/proc/base.c +7 -9
index 9d14215..14e7b73 100644
@@ -1366,16 +1366,16 @@ static ssize_t proc_fail_nth_write(struct file *file, const char __user *buf,
        int err;
        unsigned int n;
 
+       err = kstrtoint_from_user(buf, count, 0, &n);
+       if (err)
+               return err;
+
        task = get_proc_task(file_inode(file));
        if (!task)
                return -ESRCH;
+       task->fail_nth = n;
        put_task_struct(task);
-       if (task != current)
-               return -EPERM;
-       err = kstrtouint_from_user(buf, count, 0, &n);
-       if (err)
-               return err;
-       current->fail_nth = n;
+
        return count;
 }
 
@@ -1389,11 +1389,9 @@ static ssize_t proc_fail_nth_read(struct file *file, char __user *buf,
        task = get_proc_task(file_inode(file));
        if (!task)
                return -ESRCH;
-       put_task_struct(task);
-       if (task != current)
-               return -EPERM;
        len = snprintf(numbuf, sizeof(numbuf), "%u\n", task->fail_nth);
        len = simple_read_from_buffer(buf, count, ppos, numbuf, len);
+       put_task_struct(task);
 
        return len;
 }

fs/proc/base.c static const struct pid_entry tid_base_stuff[] = { · line 3359

Quoted 9 lines
fs/proc/base.c +1 -5
@@ -3358,11 +3356,7 @@ static const struct pid_entry tid_base_stuff[] = {
 #endif
 #ifdef CONFIG_FAULT_INJECTION
        REG("make-it-fail", S_IRUGO|S_IWUSR, proc_fault_inject_operations),
-       /*
-        * Operations on the file check that the task is current,
-        * so we create it with 0666 to support testing under unprivileged user.
-        */
-       REG("fail-nth", 0666, proc_fail_nth_operations),
+       REG("fail-nth", 0644, proc_fail_nth_operations),

/\/\/\/\/\/\

Quoted 4 lines

This breaks us.
Under setuid sandbox test threads can't open the file anymore. And we can't pre-open the files before dropping privs as new threads can be created afterwards.

Could you provide a working example for this? Because I'm not sure I understand the problem you described here.

If we omit resetting tsk->fail_nth in dup_task_struct(), tsk->fail_nth is inherited from parent to child process. So the parent process can pre-open and set fail-nth file and reset parent's own ->fail_nth after fork by writing 0 to fail-nth file. Does that fix your problem?

Quoted 8 lines

I think the root cause of all these problems (permissions, parsing, serialization, broken cat, symmetry) is that we are trying to fit a programmatic API into reads and writes on textual files. We don't need symmetry, we don't need read+write to reset injection, we don't need parsing and serialization, it does not make sense to do this of non-current task, it definitely does not make sense to cat this, etc.

What do you think of 2 ioctls on /sys/kernel/debug/fail_nth?

I think the misc device is suitable than debugfs file for ioctl only knob. But I prefer read/write interface than ioctl if possible.

Quoted 6 lines
Quoted 6 lines
fs/proc/base.c +0 -1
 #endif
 #ifdef CONFIG_TASK_IO_ACCOUNTING
        ONE("io",       S_IRUSR, proc_tid_io_accounting),
--

2.7.4

#11 lore raw

Re: [PATCH -mm 4/5] fault-inject: simplify access check for fail-nth

Quoted 68 lines · Akinobu Mita

2017-04-08 5:45 GMT+09:00 Dmitry Vyukov <[email protected]>:

Quoted 67 lines · Akinobu Mita
Quoted 54 lines

The fail-nth file is created with 0666 and the access is permitted if and only if the task is current.

This file is owned by the currnet user. So we can create it with 0644 and allow the owner to write it. This enables to watch the status of task->fail_nth from another processes.

---
 fs/proc/base.c | 22 ++++++++--------------
 1 file changed, 8 insertions(+), 14 deletions(-)
fs/proc/base.c +7 -9
index 9d14215..14e7b73 100644
@@ -1366,16 +1366,16 @@ static ssize_t proc_fail_nth_write(struct file *file, const char __user *buf,
        int err;
        unsigned int n;
 
+       err = kstrtoint_from_user(buf, count, 0, &n);
+       if (err)
+               return err;
+
        task = get_proc_task(file_inode(file));
        if (!task)
                return -ESRCH;
+       task->fail_nth = n;
        put_task_struct(task);
-       if (task != current)
-               return -EPERM;
-       err = kstrtouint_from_user(buf, count, 0, &n);
-       if (err)
-               return err;
-       current->fail_nth = n;
+
        return count;
 }
 
@@ -1389,11 +1389,9 @@ static ssize_t proc_fail_nth_read(struct file *file, char __user *buf,
        task = get_proc_task(file_inode(file));
        if (!task)
                return -ESRCH;
-       put_task_struct(task);
-       if (task != current)
-               return -EPERM;
        len = snprintf(numbuf, sizeof(numbuf), "%u\n", task->fail_nth);
        len = simple_read_from_buffer(buf, count, ppos, numbuf, len);
+       put_task_struct(task);
 
        return len;
 }

fs/proc/base.c static const struct pid_entry tid_base_stuff[] = { · line 3359

Quoted 9 lines
fs/proc/base.c +1 -5
@@ -3358,11 +3356,7 @@ static const struct pid_entry tid_base_stuff[] = {
 #endif
 #ifdef CONFIG_FAULT_INJECTION
        REG("make-it-fail", S_IRUGO|S_IWUSR, proc_fault_inject_operations),
-       /*
-        * Operations on the file check that the task is current,
-        * so we create it with 0666 to support testing under unprivileged user.
-        */
-       REG("fail-nth", 0666, proc_fail_nth_operations),
+       REG("fail-nth", 0644, proc_fail_nth_operations),

/\/\/\/\/\/\

Quoted 7 lines
Quoted 4 lines

This breaks us.
Under setuid sandbox test threads can't open the file anymore. And we can't pre-open the files before dropping privs as new threads can be created afterwards.

Could you provide a working example for this? Because I'm not sure I understand the problem you described here.

The actual use case is syzkaller executor:
https://github.com/google/syzkaller/blob/fault_inject/executor/executor.cc
(search for "fail-nth"). When sandbox_setuid is enabled, open of the file fails with EPERM.

Quoted 4 lines

If we omit resetting tsk->fail_nth in dup_task_struct(), tsk->fail_nth is inherited from parent to child process. So the parent process can pre-open and set fail-nth file and reset parent's own ->fail_nth after fork by writing 0 to fail-nth file. Does that fix your problem?

I don't think so.
First clone does unknown number of allocations. Second I don't need/want the thread to start failing start away. At some point in future it _may_ need to inject failures into a particular system call. There are multiple threads, and we don't know apriori which one of them will need to inject failures, since thread assignment is dynamic and depends on which system calls will block and which will not block.

Quoted 11 lines
Quoted 8 lines

I think the root cause of all these problems (permissions, parsing, serialization, broken cat, symmetry) is that we are trying to fit a programmatic API into reads and writes on textual files. We don't need symmetry, we don't need read+write to reset injection, we don't need parsing and serialization, it does not make sense to do this of non-current task, it definitely does not make sense to cat this, etc.

What do you think of 2 ioctls on /sys/kernel/debug/fail_nth?

I think the misc device is suitable than debugfs file for ioctl only knob. But I prefer read/write interface than ioctl if possible.

Why do you prefer read/write?
This interface is not meant to be used from command line, like lots of other system calls.
We did /sys/kernel/debug/kcov with ioctls and it works perfect.

Quoted 6 lines
Quoted 6 lines
Quoted 6 lines
fs/proc/base.c +0 -1
 #endif
 #ifdef CONFIG_TASK_IO_ACCOUNTING
        ONE("io",       S_IRUSR, proc_tid_io_accounting),
--

2.7.4

author maintainer
#12 lore raw

PATCH 5/5 fault-inject: add /proc/<pid>/fail-nth

In reply to Akinobu Mita

fail-nth interface is only created in /proc/self/task/<current-tid>/. This change also adds it in /proc/<pid>/.

This makes shell based tool a bit simpler.

	$ bash -c "builtin echo 100 > /proc/self/fail-nth && exec ls /"
---
 Documentation/fault-injection/fault-injection.txt | 3 ++-
 fs/proc/base.c                                    | 1 +
 2 files changed, 3 insertions(+), 1 deletion(-)
Documentation/fault-injection/fault-injection.txt +2 -1
index 370ddcb..918972b 100644
@@ -136,7 +136,8 @@ use the boot option:
 
 o proc entries
 
-- /proc/self/task/<current-tid>/fail-nth:
+- /proc/<pid>/fail-nth:
+- /proc/self/task/<tid>/fail-nth:
 
 	Write to this file of integer N makes N-th call in the task fail.
 	Read from this file returns a integer value. A value of '0' indicates
fs/proc/base.c +1 -0
index 14e7b73..ea1039d 100644
@@ -2964,6 +2964,7 @@ static const struct pid_entry tgid_base_stuff[] = {
 #endif
 #ifdef CONFIG_FAULT_INJECTION
 	REG("make-it-fail", S_IRUGO|S_IWUSR, proc_fault_inject_operations),
+	REG("fail-nth", 0644, proc_fail_nth_operations),
 #endif
 #ifdef CONFIG_ELF_CORE
 	REG("coredump_filter", S_IRUGO|S_IWUSR, proc_coredump_filter_operations),
Signature · 1 line
2.7.4