diff options
author | Tejun Heo <tj@kernel.org> | 2014-01-10 08:57:18 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2014-01-10 16:44:25 -0500 |
commit | d92d2e6bd72b653f9811e0c9c46307c743b3fc58 (patch) | |
tree | 3d9f3207bf1debe9fef016aea5e9eb23af91d8d6 /fs/kernfs/file.c | |
parent | 08da2012e0bb0f3f1422cce3f76c36a90da366b5 (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.c | 51 |
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 | */ | ||
79 | static 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 | |||
57 | static void *kernfs_seq_start(struct seq_file *sf, loff_t *ppos) | 89 | static 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) | |||
99 | static void kernfs_seq_stop(struct seq_file *sf, void *v) | 139 | static 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 | ||