Received: by 2002:ab2:620c:0:b0:1ef:ffd0:ce49 with SMTP id o12csp1700313lqt; Wed, 20 Mar 2024 11:17:09 -0700 (PDT) X-Forwarded-Encrypted: i=3; AJvYcCWBWkyqiTwps9KHSNK9AvtacZr4vZOpRjYKbdsu13O+f9uywASmbSq8Rsg95pTpxaxQiLQcJrkebwpA1u62iSRamN+SJ+u8y+GF7MqVIg== X-Google-Smtp-Source: AGHT+IFOfqwtmOL9ORoTwWwPu+mG3jRQ6Vw/URgyiStnoedp+yf/P+uFrJyjoVR8UIq1Iz0XI6Lv X-Received: by 2002:a17:906:b84b:b0:a47:7bd:593e with SMTP id ga11-20020a170906b84b00b00a4707bd593emr188156ejb.33.1710958629611; Wed, 20 Mar 2024 11:17:09 -0700 (PDT) ARC-Seal: i=2; a=rsa-sha256; t=1710958629; cv=pass; d=google.com; s=arc-20160816; b=rTNtfJU2E8TTk25ZS1gXdEE4z+xSK+FtcvuPI/r7Rmzro41hsnoc19VUA+B8L5mj+s wm9BjuB09HC/pqn9MMvs7HSco/FvibmWLSahcP/By2vtGS/fYz0v6XKDqcPAxmzvHmRz se/EbfQuK9IW1N6tgMeTu3PfLgIP3LT3M+xjxKuSGFnGLcfBGAxEkuKixi+dNSknoz7/ O9KDPoUBCuMoLhVHgTzSER0mDJPFZqYg0Tmb2xki23LPYZsrT9Nv2N7TONJeg0W1U9qK h5Jte3FrWlKpnkOj7XrMzb1pUK4D2AjPssyIGh+LiMU4pDZsrREFR6X8ZPvyONsV9T37 90iQ== ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=cc:to:from:subject:message-id:references:mime-version :list-unsubscribe:list-subscribe:list-id:precedence:in-reply-to:date :dkim-signature; bh=Ysil0RDXkCVtzkTohmRbeyotl8h5MD3ps9kD7n3YY/k=; fh=5Zu+EkrzwObeC6BjZQFoIunMByrZIvQmO9ZmLgezDCo=; b=BEt1JX9UsEkjkRYijaN/vxbkOo3zn+VVZLCsrx18BkITBConhyvhM/QPHIpjr/A/rj s5j4cyjLwqR49DyWEYvOdRwn6giKfzpyugwoDDOAqH/uDC8yayYd86O0xvzcu6j/Vg0E Rtms8AeESrzIZ8sULTYtrYv2tu40W5JUuQd3SGYdDNab/D9icA0y8v5QL0lK9Ui/lNmI FO8LG4OVH3znF9KH5654gv+F1nwoB5n8+5bSrtAAD1P6PzO+n2z3F3SHJmm3dUYwQxaC kUWFcpCcnRi4AygnY4VNN7fxk37YMRgfBqojjaVrwBjbBww0+zFrWbDytuhYCt+Itwg+ zgOw==; dara=google.com ARC-Authentication-Results: i=2; mx.google.com; dkim=pass header.i=@google.com header.s=20230601 header.b=TkGY8Y9p; arc=pass (i=1 spf=pass spfdomain=flex--aliceryhl.bounces.google.com dkim=pass dkdomain=google.com dmarc=pass fromdomain=google.com); spf=pass (google.com: domain of linux-kernel+bounces-109289-linux.lists.archive=gmail.com@vger.kernel.org designates 2604:1380:4601:e00::3 as permitted sender) smtp.mailfrom="linux-kernel+bounces-109289-linux.lists.archive=gmail.com@vger.kernel.org"; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=google.com Return-Path: Received: from am.mirrors.kernel.org (am.mirrors.kernel.org. [2604:1380:4601:e00::3]) by mx.google.com with ESMTPS id x11-20020a1709064a8b00b00a4429d64635si6654016eju.750.2024.03.20.11.17.09 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 20 Mar 2024 11:17:09 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel+bounces-109289-linux.lists.archive=gmail.com@vger.kernel.org designates 2604:1380:4601:e00::3 as permitted sender) client-ip=2604:1380:4601:e00::3; Authentication-Results: mx.google.com; dkim=pass header.i=@google.com header.s=20230601 header.b=TkGY8Y9p; arc=pass (i=1 spf=pass spfdomain=flex--aliceryhl.bounces.google.com dkim=pass dkdomain=google.com dmarc=pass fromdomain=google.com); spf=pass (google.com: domain of linux-kernel+bounces-109289-linux.lists.archive=gmail.com@vger.kernel.org designates 2604:1380:4601:e00::3 as permitted sender) smtp.mailfrom="linux-kernel+bounces-109289-linux.lists.archive=gmail.com@vger.kernel.org"; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=google.com Received: from smtp.subspace.kernel.org (wormhole.subspace.kernel.org [52.25.139.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by am.mirrors.kernel.org (Postfix) with ESMTPS id 963721F24E53 for ; Wed, 20 Mar 2024 18:09:22 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 72ED76BFCD; Wed, 20 Mar 2024 18:09:12 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="TkGY8Y9p" Received: from mail-yw1-f201.google.com (mail-yw1-f201.google.com [209.85.128.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 745AF1DFC6 for ; Wed, 20 Mar 2024 18:09:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.201 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1710958151; cv=none; b=esunLfmpqUEfIMOJ4w+iAQK9Rrfga48d2+OJ+Hlmq1hSmU5mUHLoiYW9qFkcr20NTl8FFnWt2JFRVBb08JmsftdVnyoJ+ddXS15kM2LSSkDP7aIivNsbwIMneQRJbB/k/5ZT/EBuPSWgyDVrfiqzJuuYJwTDbWH51K68BcSWgaE= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1710958151; c=relaxed/simple; bh=YJwn0EHmN4p0C1O8WY5aZR1nYAKu4era8MiJ7mxANe0=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=CfmEaqsLGlAf1K7fXW9oIbdbLYqLB1jK6x/DG99CAeOfK66CepOt67y2RfhpeM1nA0KQtxUihAKlY02Xee/PgWwRyNsjGW6wKnm4TecLp9F22u3YKP/zoSpFlk/Nd6iBI3I7cxJ+gujBphZVelwF4gEsnXNHIdXbeS7SfOafyzQ= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--aliceryhl.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=TkGY8Y9p; arc=none smtp.client-ip=209.85.128.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--aliceryhl.bounces.google.com Received: by mail-yw1-f201.google.com with SMTP id 00721157ae682-60ab69a9e6fso15866167b3.0 for ; Wed, 20 Mar 2024 11:09:09 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1710958148; x=1711562948; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=Ysil0RDXkCVtzkTohmRbeyotl8h5MD3ps9kD7n3YY/k=; b=TkGY8Y9phbftfHMOkeiOTQO8zgdXhNP1xZDqUTkLUxrgqg0ovJHnfVWKTYA4vktd5f KtgJtlhx+iYAJADqp1QgeZeupiEFZHGtOFoJeD+5rBWYmxcdS3FlAJ3L7Ven44+evw3m wh3zdBDZ51IqDxZ3VQBnodJO1Kii94pQ4t1VNCuR2aW8UsWND1ShJ37uwsQXoiw8qgtk V17vvbWWMVQEk56fomjK/kVhCOa3K2JMEI3Q7KqtCNxbev0UfdoJa7xG/EzIOD1wvAOB /FZVIH7FxeIB2T7DLVwdPF+wmEiNWcA41E+4+8u7FSbPvJp2YtyCcT9KSEFMa7lpmvYz vYzw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1710958148; x=1711562948; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=Ysil0RDXkCVtzkTohmRbeyotl8h5MD3ps9kD7n3YY/k=; b=l25fxslljWA/nb08T0QyoSomWTlUZ5bgq2Igbg4l+0bWMoMXXaIaDBOaCE52/pH/o4 1OAfbw9TSFkYc3stbhWKHOCN5+VkNnCv7Fz6fKDKIkOK6BF1iRy1tsJ7Knj2OjFdTasD 7WSYY4aPEzzizkiNZdf2pSt8oBGUqhj0CDznnzwYP4DNio0ob+I4UjHRYAcHMGgo47t9 AVzqGlbc1JqNdkD3uCuDe1j6GBhlsek7z5wxQwvEJJfFNmabtJWlYMxy3NTYT6d1crtH OqBTPXzxvHB13FqN/8krIrMDM3vIAkhOiq3E+wpP2zhOoJnZr/NjDn2Wzq8bPygXGfSf CHcg== X-Forwarded-Encrypted: i=1; AJvYcCU4dDU24+Kdmyra7x6V/tL7+7vOe2YVor7w0vU8LiOdkdpABFAeajQ9VgXB49UETO0xEKaKnw/r/326w3rjN0oMBfN76unV7S7Ne8+G X-Gm-Message-State: AOJu0Yxp8CPDnC6+QS+fDiNIXJae4dWj3NGakJjCfYaul+NpENJz16p/ HDVMA15miAWpe3KlBroFhPopPrXKPq4WybCd94fd5gR3yCZoEPXTS3c7EjzRazgRcUvEpyRyuzs CC8CaIlTx7kCsGw== X-Received: from aliceryhl2.c.googlers.com ([fda3:e722:ac3:cc00:68:949d:c0a8:572]) (user=aliceryhl job=sendgmr) by 2002:a81:494f:0:b0:611:537:2c0f with SMTP id w76-20020a81494f000000b0061105372c0fmr9291ywa.2.1710958148515; Wed, 20 Mar 2024 11:09:08 -0700 (PDT) Date: Wed, 20 Mar 2024 18:09:05 +0000 In-Reply-To: <20240320-wegziehen-teilhaben-86e071fa163c@brauner> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240320-wegziehen-teilhaben-86e071fa163c@brauner> X-Mailer: git-send-email 2.44.0.291.gc1ea87d7ee-goog Message-ID: <20240320180905.3858535-1-aliceryhl@google.com> Subject: Re: [PATCH v5 3/9] rust: file: add Rust abstraction for `struct file` From: Alice Ryhl To: brauner@kernel.org Cc: a.hindborg@samsung.com, alex.gaynor@gmail.com, aliceryhl@google.com, arve@android.com, benno.lossin@proton.me, bjorn3_gh@protonmail.com, boqun.feng@gmail.com, cmllamas@google.com, dan.j.williams@intel.com, dxu@dxuuu.xyz, gary@garyguo.net, gregkh@linuxfoundation.org, joel@joelfernandes.org, keescook@chromium.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, maco@android.com, ojeda@kernel.org, peterz@infradead.org, rust-for-linux@vger.kernel.org, surenb@google.com, tglx@linutronix.de, tkjos@android.com, tmgross@umich.edu, viro@zeniv.linux.org.uk, wedsonaf@gmail.com, willy@infradead.org, yakoyoku@gmail.com Content-Type: text/plain; charset="utf-8" Christian Brauner wrote: > On Fri, Feb 09, 2024 at 11:18:16AM +0000, Alice Ryhl wrote: >> From: Wedson Almeida Filho >> >> This abstraction makes it possible to manipulate the open files for a >> process. The new `File` struct wraps the C `struct file`. When accessing >> it using the smart pointer `ARef`, the pointer will own a >> reference count to the file. When accessing it as `&File`, then the >> reference does not own a refcount, but the borrow checker will ensure >> that the reference count does not hit zero while the `&File` is live. >> >> Since this is intended to manipulate the open files of a process, we >> introduce an `fget` constructor that corresponds to the C `fget` >> method. In future patches, it will become possible to create a new fd in >> a process and bind it to a `File`. Rust Binder will use these to send >> fds from one process to another. >> >> We also provide a method for accessing the file's flags. Rust Binder >> will use this to access the flags of the Binder fd to check whether the >> non-blocking flag is set, which affects what the Binder ioctl does. >> >> This introduces a struct for the EBADF error type, rather than just >> using the Error type directly. This has two advantages: >> * `File::from_fd` returns a `Result, BadFdError>`, which the > > Sorry, where's that method? Sorry, this is supposed to say `File::fget`. >> +/// Wraps the kernel's `struct file`. >> +/// >> +/// This represents an open file rather than a file on a filesystem. Processes generally reference >> +/// open files using file descriptors. However, file descriptors are not the same as files. A file >> +/// descriptor is just an integer that corresponds to a file, and a single file may be referenced >> +/// by multiple file descriptors. >> +/// >> +/// # Refcounting >> +/// >> +/// Instances of this type are reference-counted. The reference count is incremented by the >> +/// `fget`/`get_file` functions and decremented by `fput`. The Rust type `ARef` represents a >> +/// pointer that owns a reference count on the file. >> +/// >> +/// Whenever a process opens a file descriptor (fd), it stores a pointer to the file in its `struct >> +/// files_struct`. This pointer owns a reference count to the file, ensuring the file isn't >> +/// prematurely deleted while the file descriptor is open. In Rust terminology, the pointers in >> +/// `struct files_struct` are `ARef` pointers. >> +/// >> +/// ## Light refcounts >> +/// >> +/// Whenever a process has an fd to a file, it may use something called a "light refcount" as a >> +/// performance optimization. Light refcounts are acquired by calling `fdget` and released with >> +/// `fdput`. The idea behind light refcounts is that if the fd is not closed between the calls to >> +/// `fdget` and `fdput`, then the refcount cannot hit zero during that time, as the `struct >> +/// files_struct` holds a reference until the fd is closed. This means that it's safe to access the >> +/// file even if `fdget` does not increment the refcount. >> +/// >> +/// The requirement that the fd is not closed during a light refcount applies globally across all >> +/// threads - not just on the thread using the light refcount. For this reason, light refcounts are >> +/// only used when the `struct files_struct` is not shared with other threads, since this ensures >> +/// that other unrelated threads cannot suddenly start using the fd and close it. Therefore, >> +/// calling `fdget` on a shared `struct files_struct` creates a normal refcount instead of a light >> +/// refcount. > > When the fdget() calling task doesn't have a shared file descriptor > table fdget() will not increment the reference count, yes. This > also implies that you cannot have task A use fdget() and then pass > f.file to task B that holds on to it while A returns to userspace. It's > irrelevant that task A won't drop the reference count or that task B > won't drop the reference count. Because task A could return back to > userspace and immediately close the fd via a regular close() system call > at which point task B has a UAF. In other words a file that has been > gotten via fdget() can't be Send to another task without the Send > implying taking a reference to it. That matches my understanding. I suppose that technically you can still send it to another thread *if* you ensure that the current thread waits until that other thread stops using the file before returning to userspace. >> +/// >> +/// Light reference counts must be released with `fdput` before the system call returns to >> +/// userspace. This means that if you wait until the current system call returns to userspace, then >> +/// all light refcounts that existed at the time have gone away. >> +/// >> +/// ## Rust references >> +/// >> +/// The reference type `&File` is similar to light refcounts: >> +/// >> +/// * `&File` references don't own a reference count. They can only exist as long as the reference >> +/// count stays positive, and can only be created when there is some mechanism in place to ensure >> +/// this. >> +/// >> +/// * The Rust borrow-checker normally ensures this by enforcing that the `ARef` from which >> +/// a `&File` is created outlives the `&File`. > > The section confuses me a little: Does the borrow-checker always ensure > that a &File stays valid or are there circumstances where it doesn't or > are you saying it doesn't enforce it? The borrow-checker always ensures it. A &File is actually short-hand for &'a File, where 'a is some unspecified lifetime. We say that &'a File is annotated with 'a. The borrow-checker rejects any code that tries to use a reference after the end of the lifetime annotated on it. So as long as you annotate the reference with a sufficiently short lifetime, the borrow checker will prevent UAF. And outside of cases like from_ptr, the borrow-checker also takes care of ensuring that the lifetimes are sufficiently short. (Technically &'a File and &'b File are two completely different types, so &File is technically a class of types and not a single type. Rust will automatically convert &'long File to &'short File.) >> +/// >> +/// * Using the unsafe [`File::from_ptr`] means that it is up to the caller to ensure that the >> +/// `&File` only exists while the reference count is positive. > > What is this used for in binder? If it's not used don't add it. This is used on the boundary between the Rust part of Binder and the binderfs component that is implemented in C. For example: unsafe extern "C" fn rust_binder_open( inode: *mut bindings::inode, file_ptr: *mut bindings::file, ) -> core::ffi::c_int { // SAFETY: The `rust_binderfs.c` file ensures that `i_private` is set to the return value of a // successful call to `rust_binder_new_device`. let ctx = unsafe { Arc::::borrow((*inode).i_private) }; // SAFETY: The caller provides a valid file pointer to a new `struct file`. let file = unsafe { File::from_ptr(file_ptr) }; let process = match Process::open(ctx, file) { Ok(process) => process, Err(err) => return err.to_errno(), }; // SAFETY: This file is associated with Rust binder, so we own the `private_data` field. unsafe { (*file_ptr).private_data = process.into_foreign().cast_mut(); } 0 } Here, rust_binder_open is the open function in a struct file_operations vtable. In this case, file_ptr is guaranteed by the caller to be valid for the duration of the call to rust_binder_open. Binder uses from_ptr to get a &File from the raw pointer. As far as I understand, the caller of rust_binder_open uses fdget to ensure that file_ptr is valid for the duration of the call, but the Rust code doesn't assume that it does this with fdget. As long as file_ptr is valid for the duration of the rust_binder_open call, this use of from_ptr is okay. It will continue to work even if the caller is changed to use fget. As for how this code ensures that `file` ends up annotated with a sufficiently short lifetime, well, that has to do with the signature of Process::open. Here it is: impl Process { pub(crate) fn open(ctx: ArcBorrow<'_, Context>, file: &File) -> Result> { Self::new(ctx.into(), ARef::from(file.cred())) } } In this case, &File is used without specifying a lifetime. It's a function argument, so this means that the lifetime annotated on the `file` argument will be exactly the duration in which Process::open is called. So any attempt to use `file` after the end of the call to Process::open will be rejected by the borrow-checker. (E.g., if Process::open tried to schedule something on the workqueue using `file`, then that would not compile. Storing it in a global variable would not compile either.) This means that the borrow-checker will not catch mistakes in rust_binder_open, but it *will* catch mistakes in Process::open, and anything called by Process::open. These examples come from patch 2 of the Binder RFC: https://lore.kernel.org/rust-for-linux/20231101-rust-binder-v1-2-08ba9197f637@google.com/ >> +/// >> +/// * You can think of `fdget` as using an fd to look up an `ARef` in the `struct > > Could you explain why there isn't an explicit fdget() then and you have > that from_ptr() method? I don't provide an fdget implementation because Binder never calls it. However, if you would like, I would be happy to add one to the patchset. As for from_ptr, see above. Alice