aboutsummaryrefslogtreecommitdiffstats
path: root/fs/kernfs/file.c
diff options
context:
space:
mode:
authorTejun Heo <tj@kernel.org>2014-01-10 08:57:18 -0500
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2014-01-10 16:44:25 -0500
commitd92d2e6bd72b653f9811e0c9c46307c743b3fc58 (patch)
tree3d9f3207bf1debe9fef016aea5e9eb23af91d8d6 /fs/kernfs/file.c
parent08da2012e0bb0f3f1422cce3f76c36a90da366b5 (diff)
kernfs: fix get_active failure handling in kernfs_seq_*()
When kernfs_seq_start() fails to obtain an active reference, it returns ERR_PTR(-ENODEV). kernfs_seq_stop() is then invoked with the error pointer value; however, it still proceeds to invoke kernfs_put_active() on the node leading to unbalanced put. If kernfs_seq_stop() is called even after active ref failure, it should skip invocation of @ops->seq_stop() and put_active. Unfortunately, this is a bit complicated because active ref failure isn't the only thing which may fail with ERR_PTR(-ENODEV). @ops->seq_start/next() may also fail with the error value and kernfs_seq_stop() doesn't have a way to tell apart those failures. Work it around by factoring out the active part of kernfs_seq_stop() into kernfs_seq_stop_active() and invoking it directly if @ops->seq_start/next() fail with ERR_PTR(-ENODEV) and updating kernfs_seq_stop() to skip kernfs_seq_stop_active() on ERR_PTR(-ENODEV). This is a bit nasty but ensures that the active put is skipped iff get_active failed in kernfs_seq_start(). Signed-off-by: Tejun Heo <tj@kernel.org> Cc: Sasha Levin <sasha.levin@oracle.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'fs/kernfs/file.c')
-rw-r--r--fs/kernfs/file.c51
1 files changed, 44 insertions, 7 deletions
diff --git a/fs/kernfs/file.c b/fs/kernfs/file.c
index 316604cc3a1c..bdd38854ef65 100644
--- a/fs/kernfs/file.c
+++ b/fs/kernfs/file.c
@@ -54,6 +54,38 @@ static const struct kernfs_ops *kernfs_ops(struct kernfs_node *kn)
54 return kn->attr.ops; 54 return kn->attr.ops;
55} 55}
56 56
57/*
58 * As kernfs_seq_stop() is also called after kernfs_seq_start() or
59 * kernfs_seq_next() failure, it needs to distinguish whether it's stopping
60 * a seq_file iteration which is fully initialized with an active reference
61 * or an aborted kernfs_seq_start() due to get_active failure. The
62 * position pointer is the only context for each seq_file iteration and
63 * thus the stop condition should be encoded in it. As the return value is
64 * directly visible to userland, ERR_PTR(-ENODEV) is the only acceptable
65 * choice to indicate get_active failure.
66 *
67 * Unfortunately, this is complicated due to the optional custom seq_file
68 * operations which may return ERR_PTR(-ENODEV) too. kernfs_seq_stop()
69 * can't distinguish whether ERR_PTR(-ENODEV) is from get_active failure or
70 * custom seq_file operations and thus can't decide whether put_active
71 * should be performed or not only on ERR_PTR(-ENODEV).
72 *
73 * This is worked around by factoring out the custom seq_stop() and
74 * put_active part into kernfs_seq_stop_active(), skipping it from
75 * kernfs_seq_stop() if ERR_PTR(-ENODEV) while invoking it directly after
76 * custom seq_file operations fail with ERR_PTR(-ENODEV) - this ensures
77 * that kernfs_seq_stop_active() is skipped only after get_active failure.
78 */
79static void kernfs_seq_stop_active(struct seq_file *sf, void *v)
80{
81 struct kernfs_open_file *of = sf->private;
82 const struct kernfs_ops *ops = kernfs_ops(of->kn);
83
84 if (ops->seq_stop)
85 ops->seq_stop(sf, v);
86 kernfs_put_active(of->kn);
87}
88
57static void *kernfs_seq_start(struct seq_file *sf, loff_t *ppos) 89static void *kernfs_seq_start(struct seq_file *sf, loff_t *ppos)
58{ 90{
59 struct kernfs_open_file *of = sf->private; 91 struct kernfs_open_file *of = sf->private;
@@ -69,7 +101,11 @@ static void *kernfs_seq_start(struct seq_file *sf, loff_t *ppos)
69 101
70 ops = kernfs_ops(of->kn); 102 ops = kernfs_ops(of->kn);
71 if (ops->seq_start) { 103 if (ops->seq_start) {
72 return ops->seq_start(sf, ppos); 104 void *next = ops->seq_start(sf, ppos);
105 /* see the comment above kernfs_seq_stop_active() */
106 if (next == ERR_PTR(-ENODEV))
107 kernfs_seq_stop_active(sf, next);
108 return next;
73 } else { 109 } else {
74 /* 110 /*
75 * The same behavior and code as single_open(). Returns 111 * The same behavior and code as single_open(). Returns
@@ -85,7 +121,11 @@ static void *kernfs_seq_next(struct seq_file *sf, void *v, loff_t *ppos)
85 const struct kernfs_ops *ops = kernfs_ops(of->kn); 121 const struct kernfs_ops *ops = kernfs_ops(of->kn);
86 122
87 if (ops->seq_next) { 123 if (ops->seq_next) {
88 return ops->seq_next(sf, v, ppos); 124 void *next = ops->seq_next(sf, v, ppos);
125 /* see the comment above kernfs_seq_stop_active() */
126 if (next == ERR_PTR(-ENODEV))
127 kernfs_seq_stop_active(sf, next);
128 return next;
89 } else { 129 } else {
90 /* 130 /*
91 * The same behavior and code as single_open(), always 131 * The same behavior and code as single_open(), always
@@ -99,12 +139,9 @@ static void *kernfs_seq_next(struct seq_file *sf, void *v, loff_t *ppos)
99static void kernfs_seq_stop(struct seq_file *sf, void *v) 139static void kernfs_seq_stop(struct seq_file *sf, void *v)
100{ 140{
101 struct kernfs_open_file *of = sf->private; 141 struct kernfs_open_file *of = sf->private;
102 const struct kernfs_ops *ops = kernfs_ops(of->kn);
103 142
104 if (ops->seq_stop) 143 if (v != ERR_PTR(-ENODEV))
105 ops->seq_stop(sf, v); 144 kernfs_seq_stop_active(sf, v);
106
107 kernfs_put_active(of->kn);
108 mutex_unlock(&of->mutex); 145 mutex_unlock(&of->mutex);
109} 146}
110 147