From 8b7b69c978381cfa964d80a9b3eee991015f3056 Mon Sep 17 00:00:00 2001 From: Jamie Liu Date: Tue, 7 Jan 2025 20:21:58 -0800 Subject: [PATCH] tmpfs: limit regularFile.Translate() fill range When e.g. an application thread takes a page fault on an mmapped file, MM calls `memmap.Mappable.Translate()` to obtain the corresponding host FD range that should be mapped into the application's address space. It passes both the range that *must* be mapped (e.g. the faulting page) as `required`, and the maximum range that *may* be mapped (the previously-unfaulted part of the corresponding VMA) as `optional`, such that file implementations can map more than `required` to avoid future page faults. Prior to this CL, `tmpfs.regularFile.Translate()` always returned translations up to `optional`, under the assumption that allocating larger ranges from `pgalloc.MemoryFile` has negligible incremental cost. This behavior dates to the introduction of `memmap.Mappable.Translate()` (cl/182882705) and thus predates the implementation of tmpfs size limits (cl/442686814). Now that the latter exists, unconditionally translating - and therefore allocating pages - up to `optional` can result in hitting tmpfs size limits prematurely. Thus: Constrain optional translations returned by `tmpfs.regularFile.Translate()`, applying the same logic as `gofer.maxFillRange()`. PiperOrigin-RevId: 713134287 --- pkg/sentry/fsimpl/tmpfs/regular_file.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pkg/sentry/fsimpl/tmpfs/regular_file.go b/pkg/sentry/fsimpl/tmpfs/regular_file.go index c4f35cd5e..d6c0737e9 100644 --- a/pkg/sentry/fsimpl/tmpfs/regular_file.go +++ b/pkg/sentry/fsimpl/tmpfs/regular_file.go @@ -309,6 +309,19 @@ func (rf *regularFile) Translate(ctx context.Context, required, optional memmap. if optional.End > pgend { optional.End = pgend } + // Constrain allocation to at most maxOptionalBytes or required.Length(), + // whichever is greater. + const maxOptionalBytes = 64 << 10 // 64 KB, arbitrarily matches Linux's default fault_around_pages + if required.Length() >= maxOptionalBytes { + optional = required + } else { + if optional.Length() > maxOptionalBytes { + optional.Start = required.Start + if optional.Length() > maxOptionalBytes { + optional.End = optional.Start + maxOptionalBytes + } + } + } pagesToFill := rf.data.PagesToFill(required, optional) if !rf.inode.fs.accountPages(pagesToFill) { // If we can not accommodate pagesToFill pages, then retry with just