[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <YHsRdTqgurSCykt7@zeniv-ca.linux.org.uk>
Date: Sat, 17 Apr 2021 16:48:53 +0000
From: Al Viro <viro@...iv.linux.org.uk>
To: Alexei Starovoitov <alexei.starovoitov@...il.com>
Cc: "David S. Miller" <davem@...emloft.net>,
Daniel Borkmann <daniel@...earbox.net>,
Andrii Nakryiko <andrii@...nel.org>,
Network Development <netdev@...r.kernel.org>,
bpf <bpf@...r.kernel.org>, Kernel Team <kernel-team@...com>
Subject: Re: [PATCH bpf-next 11/15] bpf: Add bpf_sys_close() helper.
On Sat, Apr 17, 2021 at 07:36:39AM -0700, Alexei Starovoitov wrote:
> The kernel will perform the same work with FDs. The same locks are held
> and the same execution conditions are in both cases. The LSM hooks,
> fsnotify, etc will be called the same way.
> It's no different if new syscall was introduced "sys_foo(int num)" that
> would do { return close_fd(num); }.
> It would opearate in the same user context.
Hmm... unless I'm misreading the code, one of the call chains would seem to
be sys_bpf() -> bpf_prog_test_run() -> ->test_run() -> ... -> bpf_sys_close().
OK, as long as you make sure bpf_prog_get() does fdput() (i.e. that we
don't have it restructured so that fdget()/fdput() pair would be lifted into
bpf_prog_test_run(), with fdput() moved in place of bpf_prog_put()).
Note that we *really* can not allow close_fd() on anything to be bracketed
by fdget()/fdput() pair; we had bugs of that sort and, as the matter of fact,
still have one in autofs_dev_ioctl().
The trouble happens if you have file F with 2 references, held by descriptor
tables of different processes. Say, process A has descriptor 6 refering to
it, while B has descriptor 42 doing the same. Descriptor tables of A and B
are not shared with anyone.
A: fdget(6) -> returns a reference to F, refcount _not_ touched
A: close_fd(6) -> rips the reference to F from descriptor table, does fput(F)
refcount drops to 1.
B: close(42) -> rips the reference to F from B's descriptor table, does fput(F)
This time refcount does reach 0 and we use task_work_add() to
make sure the destructor (__fput()) runs before B returns to
userland. sys_close() returns and B goes off to userland.
On the way out __fput() is run, and among other things,
->release() of F is executed, doing whatever it wants to do.
F is freed.
And at that point A, which presumably is using the guts of F, gets screwed.
In case of autofs_dev_ioctl(), it's possible for a thread to end up blocked
inside copy_to_user(), with autofs functions in call chains *AND* module
refcount of autofs not pinned by anything. The effects of returning into a
function that used to reside in now unmapped page are obviously not pretty...
Basically, the rule is
* never remove from descriptor table if you currently have an outstadning
fdget() (i.e. without having done the matching fdput() yet).
That, obviously, covers all ioctls - there we have fdget() done
by sys_ioctl() on the issuing descriptor. In your case you seem to be
safe, but it's a bloody dangerous minefield - you really need a big warning
in all call sites. The worst part is that it won't happen with intended
use, so it doesn't show up in routine regression testing. In particular,
for autofs the normal case is AUTOFS_DEV_IOCTL_CLOSEMOUNT getting passed
a file descriptor of something mounted and *not* the descriptor of
/dev/autofs we are holding fdget() on. However, there's no way to prevent
a malicious call when we pass exactly that.
So please, mark all call sites with "make very sure you never get
here with unpaired fdget()".
BTW, if my reading (re ->test_run()) is correct, what limits the recursion
via bpf_sys_bpf()?
Powered by blists - more mailing lists