Re: [PATCH] seq_read: move count check against iov_iter_count after calling op show
From: Xin Long <lucien.xin@gmail.com>
Date: 2021-02-04 05:54:57
On Thu, Feb 4, 2021 at 1:46 PM NeilBrown [off-list ref] wrote:
On Thu, Feb 04 2021, Xin Long wrote:quoted
Hi, Neil, This is a kind of urgent issue, and I suggest going with the "m->index++" one in both traverse() and seq_read_iter() first. Once you have a better fix, you can follow up after. Sounds good?I assumed you would be working on the better fix based on my feedback. I guess not. In that case I had better prepare one. I'll try to have something on Monday.
Thanks, we'll be waiting for your better fix, :-).
As for "going with" your patch, it isn't my place to accept or reject your patch - that is the maintainer's responsibility. I think your patch is wrong, so I cannot recommend it.
okay.
NeilBrownquoted
On Fri, Jan 29, 2021 at 2:57 PM Xin Long [off-list ref] wrote:quoted
Hi, Neil, Thanks for reviewing, more below. On Fri, Jan 29, 2021 at 6:56 AM NeilBrown [off-list ref] wrote:quoted
On Fri, Jan 22 2021, Xin Long wrote:quoted
In commit 1f4aace60b0e ("fs/seq_file.c: simplify seq_file iteration code and interface"), it broke a behavior: op show() is always called when op next() returns an available obj.Interesting. I was not aware that some callers assumed this guarantee. If we are going to support it (which seems reasonable) we should add a statement of this guarantee to the documentation - Documentation/filesystems/seq_file.rst. Maybe a new paragraph after "Finally, the show() function ..." Note that show() will *always* be called after a successful start() or next() call, so that it can release any resources (such as ref-counts) that was acquired by those calls.OK, that's good, will add it.quoted
quoted
This caused a refcnt leak in net/sctp/proc.c, as of the seq_operations sctp_assoc_ops, transport obj is held in op next() and released in op show(). Here fix it by moving count check against iov_iter_count after calling op show() so that op show() can still be called when op next() returns an available obj. Note that m->index needs to increase so that op start() could go fetch the next obj in the next round.This is certainly wrong. As the introduction in my patch said: A large part of achieving this is to *always* call ->next after ->show has successfully stored all of an entry in the buffer. Never just increment the index instead.Understand.quoted
Incrementing ->index in common seq_file code is wrong. As we are no longer calling ->next after a successful ->show, we need to make that ->show appear unsuccessful so that it will be retried. This is done be setting "m->count = offs". So the moved code below becomes if (m->count >= iov_iter_count(iter)) { /* That record is more than we want, so discard it */ m->count = offs; break; }But I'm not sure if this's a better way, as discarding it means the last show() call is just a waste, next time it has to call show() for that obj again. Note that this is a different case from [1] (show() call actually failed) and [2](the buffer overflowed), and it makes sense to call show() again due to [1] and [2] next time. if (err > 0) { <---[1] m->count = offs; } else if (err || seq_has_overflowed(m)) { <--- [2] m->count = offs; break; } if (m->count >= iov_iter_count(iter)) { <---[3] But for this one [3], all it needs is just enter into seq_read again and do the copying, no need to discard it.quoted
Possibly that can be merged into the preceding 'if'. Also the traverse() function contains a call to ->next that is not reliably followed by a call to ->show, even when successful. That needs to be fixed too.Right, But I don't see a way here other than Incrementing m->index in traverse():@@ -114,16 +114,19 @@ static int traverse(struct seq_file *m, loff_t offset) } if (seq_has_overflowed(m)) goto Eoverflow; - p = m->op->next(m, p, &m->index); if (pos + m->count > offset) { m->from = offset - pos; m->count -= m->from; + m->index++; break; } pos += m->count; m->count = 0; - if (pos == offset) + if (pos == offset) { + m->index++; break; + } + p = m->op->next(m, p, &m->index); }quoted
Thanks, NeilBrownquoted
Fixes: 1f4aace60b0e ("fs/seq_file.c: simplify seq_file iteration code and interface") Reported-by: Prijesh <redacted> Signed-off-by: Xin Long <lucien.xin@gmail.com> --- fs/seq_file.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)diff --git a/fs/seq_file.c b/fs/seq_file.c index 03a369c..da304f7 100644 --- a/fs/seq_file.c +++ b/fs/seq_file.c@@ -264,8 +264,6 @@ ssize_t seq_read_iter(struct kiocb *iocb, struct iov_iter *iter) } if (!p || IS_ERR(p)) // no next record for us break; - if (m->count >= iov_iter_count(iter)) - break; err = m->op->show(m, p); if (err > 0) { // ->show() says "skip it" m->count = offs;@@ -273,6 +271,10 @@ ssize_t seq_read_iter(struct kiocb *iocb, struct iov_iter *iter) m->count = offs; break; } + if (m->count >= iov_iter_count(iter)) { + m->index++; + break; + } } m->op->stop(m, p); n = copy_to_iter(m->buf, m->count, iter); --2.1.0