190 Commits
Author SHA1 Message Date
Jing ChenandgVisor bot e550bfbd05 Have lgetxattr return ENODATA.
It is a workaround to lgetxattr/lsetxattr working with security.capacity when running docker in gvisor.

PiperOrigin-RevId: 563194162
2023-09-06 13:13:19 -07:00
Nicolas LacasseandgVisor bot 268b43d1b2 tmpfs: Allow non-standard xattr prefixes.
PiperOrigin-RevId: 561105602
2023-08-29 12:31:01 -07:00
Andrei VaginandgVisor bot eb6b3ac00b vfs: MountNamespace.Root() has to return a top mount of /
A few mounts can be mounted on top of `/`.

PiperOrigin-RevId: 558264274
2023-08-18 15:35:47 -07:00
Andrei VaginandgVisor bot aa2c8c33c6 Implement setns for mount namespaces
PiperOrigin-RevId: 552859231
2023-08-01 11:12:29 -07:00
Ayush RanjanandgVisor bot e54e3668b0 Impose default tmpfs size limits correctly.
Syzkaller came up with workloads that fallocate(2) 1 TB in /tmp. The host
mlock(2) or madvise(2) syscalls on memfd(2) files end up hanging for multiple
minutes in such situations causing the watchdog to mark the calling goroutine
as stuck. memfd(2) files have not size limits.

Linux fails such fallocate(2) attempts in /tmp with ENOSPC.
In Linux tmpfs (shmem), when size= mount option is not specified, the default
size limit for the mount is set to 50% of physical RAM size. But in gVisor, it
is set to MaxInt64. Which is why Linux fails with ENOSPC and gVisor doesn't.

In runsc, the physcial RAM size is already exposed to the containerized
application via `/proc/meminfo` which uses `usage.*TotalMemoryBytes`. These
fields are configured using the `MemTotal:` field from host `/proc/meminfo`.
So use that information to set the default size limit correctly.

Reported-by: syzbot+4aa3d6d42b063a11c850@syzkaller.appspotmail.com
PiperOrigin-RevId: 548252095
2023-07-14 16:44:25 -07:00
Ayush RanjanandgVisor bot 0d52b50f83 Use write(2) host syscall to perform writes on disk-backed MemoryFiles.
Prepopulating pages for disk-backed MemoryFiles has proved to be futile.
The mf.MapInternal()+safemem.CopySeq() approach used right now incurs a lot of
page faults without page population. Page-by-page faults incurs a lot of
context switching.

On the other hand, the write syscall makes one context switch to kernel, and
faults all the pages that are touched during write. Note that safemem.CopySeq()
avoids a syscall and hence can be faster sometimes when the underlying page
is populated. But with disk writebacks, it is hard to predict/account what is
populated. Writebacks can happen asynchronously based on system load.

Benchmark results show that FIO write performance improves a lot on rootfs:
```
goos: linux
goarch: amd64
cpu: Intel(R) Xeon(R) CPU @ 2.80GHz
                                                       │ benchout.runsc-before │       benchout.runsc-after       │
                                                       │        sec/op         │   sec/op    vs base              │
BuildABSL/page_cache.clean/filesystem.bindfs-4                      90.20 ± 2%   89.44 ± 1%       ~ (p=0.382 n=8)
BuildGRPC/page_cache.clean/filesystem.bindfs-4                      626.0 ± 1%   626.8 ± 0%       ~ (p=0.505 n=8)
RubySpecTest/page_cache.clean/filesystem.bindfs-4                   52.11 ± 1%   52.37 ± 1%       ~ (p=0.105 n=8)
Fio/operation.write/blockSize.4K/filesystem.rootfs-4                2.509m ± 0%   2.509m ± 0%        ~ (p=0.878 n=8)
Fio/operation.write/blockSize.64K/filesystem.rootfs-4               2.009m ± 0%   1.507m ± 0%  -24.98% (p=0.000 n=8)
Fio/operation.write/blockSize.1024K/filesystem.rootfs-4             2.008m ± 0%   1.508m ± 0%  -24.90% (p=0.000 n=8)

                                                        │   benchout.runsc-before    │               benchout.runsc-after                │
                                                        │ bandwidth.bytes_per_second │ bandwidth.bytes_per_second  vs base               │
Fio/operation.write/blockSize.4K/filesystem.rootfs-4                     649.1M ± 2%                  705.2M ± 2%   +8.64% (p=0.000 n=8)
Fio/operation.write/blockSize.64K/filesystem.rootfs-4                    991.1M ± 1%                 1499.1M ± 3%  +51.25% (p=0.000 n=8)
Fio/operation.write/blockSize.1024K/filesystem.rootfs-4                  1.198G ± 2%                  1.945G ± 2%  +62.34% (p=0.000 n=8)

                                                        │ benchout.runsc-before │             benchout.runsc-after             │
                                                        │ io_ops.ops_per_second │ io_ops.ops_per_second  vs base               │
Fio/operation.write/blockSize.4K/filesystem.rootfs-4                158.5k ± 2%             172.2k ± 2%   +8.64% (p=0.000 n=8)
Fio/operation.write/blockSize.64K/filesystem.rootfs-4               15.12k ± 1%             22.87k ± 3%  +51.25% (p=0.000 n=8)
Fio/operation.write/blockSize.1024K/filesystem.rootfs-4             1.143k ± 2%             1.855k ± 2%  +62.34% (p=0.000 n=8)

                                                  │ benchout.runsc-before │    benchout.runsc-after     │
                                                  │       load.sec        │  load.sec   vs base         │
RubySpecTest/page_cache.clean/filesystem.bindfs-4              7.555 ± 1%   7.585 ± 1%  ~ (p=0.457 n=8)
```

PiperOrigin-RevId: 548021076
2023-07-13 22:32:34 -07:00
Ayush RanjanandgVisor bot dd3a7a1fb9 Simply pgalloc.MemoryFile.Allocate() API.
Earlier we had another method for AllocateAndPopulate. This change moves the
additional (optional) parameters to pgalloc.AllocOpts.

* Makes `Reader` parameter optional. Not all callers have data to fill into
  the allocated pages. Avoids having to define no-op readers.
* Avoid calling MapInternal() when `Reader == nil` and
  `Mode != AllocateAndWritePopulate`.
* Avoid calling safemem.ReadFullToBlocks() when `Reader == nil`.
* Move AllocationMode upgrade/downgrade logic to callsites. This makes
  Allocate() simpler.

PiperOrigin-RevId: 547688619
2023-07-12 22:12:08 -07:00
Nayana BidariandgVisor bot f8c4846f21 Pass memory cgroup id in pgalloc.Allocate()
For memory accounting per task, the memory cgroup id of the task is required.
This CL retrieves the cgroup id from the task context and stores it in the
AllocOpts struct which will later be used to account for memory usage.

PiperOrigin-RevId: 546996195
2023-07-10 15:16:47 -07:00
Ayush RanjanandgVisor bot f52a0f6272 Prepopulate pages for large writes in tmpfs.
When an application makes a huge write, it is better to pre-fault all the pages
that will later be touched by runtime.memmove() via safemem.CopySeq(). Earlier
we were calling MemoryFile.Allocate(), which does not populate the pages.

Otherwise, these pages fault one at a time as they are touched, which is really
slow due to context switching.

Note that this optimization only works for shmem-backed MemoryFiles. This means
that writes to unallocated regions of tmpfs mounts (like /tmp) will be faster.
rootfs (overlay) does not benefit because even though it has an upper tmpfs
layer, the MemoryFile used is disk-backed.

We only prepopulate if more than 1 page is being written to. This is because
the syscall path is susceptible to certain overheads that the page fault path
does not have. For example seccomp-bpf and syscall auditing.

The improved large writes in tmpfs is evidenced by (on Linux >=5.14):
- Before
```
$ docker run --runtime=runsc --rm ubuntu bash -c "dd if=/dev/zero of=/tmp/file.txt bs=40960 count=100000"
100000+0 records in
100000+0 records out
4096000000 bytes (4.1 GB, 3.8 GiB) copied, 3.10212 s, 1.3 GB/s
```
- After
```
docker run --runtime=runsc --rm ubuntu bash -c "dd if=/dev/zero of=/tmp/file.txt bs=40960 count=100000"
100000+0 records in
100000+0 records out
4096000000 bytes (4.1 GB, 3.8 GiB) copied, 2.38347 s, 1.7 GB/s
```

In this workload, this change reduces latency by 23% (3.10212s -> 2.38347s).

PiperOrigin-RevId: 544056038
2023-06-28 07:53:49 -07:00
Ayush RanjanandgVisor bot a056469ab5 Only populate pages for tmpfs-backed MemoryFile in tmpfs Allocate().
Pre-populating pages on fallocate(2) when the MemoryFile is backed by a disk
is wasteful.

It is currently wasteful because the mlock+munlock mechanism used in
pgalloc.tryPopulate() to populate pages does not work with some disk-backed
filesystems like ext4. We end up faulting read-only pages, which need to
be re-faulted on write anyways.

Later we will update pgalloc.tryPopulate() to use madvice(MADV_POPULATE_WRITE).
It wouldn't work in that case too because even though it does fault writable
pages, they are written back to disk before the application can write to them.
And so they will be re-faulted on write. This would actually worsen the
performance because now we have the extra cost of writeback.

Hence, avoid populating pages for disk-backed MemoryFile on fallocate(2).
Instead we just commit the pages to satisfy fallocate(2) semantics.
When the application writes to these file offsets, the pages will fault
normally.

To allow this distinction between "committing" and "populating" pages in
addition to allocating them, the pgalloc API has been extended by introducing
`pgalloc.AllocationMode` enum. It allows the caller to select if pages should
be only-allocated, allocated+committed or allocated+committed+populated.

Here are the benchmarking results:
```
goos: linux
goarch: amd64
cpu: Intel(R) Xeon(R) CPU @ 2.80GHz
                                                        │ /tmp/benchout.runsc │      /tmp/benchout.runsc-cl1       │
                                                        │       sec/op        │    sec/op     vs base              │
BuildABSL/page_cache.clean/filesystem.bindfs-4                   100.73 ± 12%    99.72 ± 58%       ~ (p=0.805 n=7)
BuildGRPC/page_cache.clean/filesystem.bindfs-4                    703.5 ±  1%    690.9 ±  1%  -1.78% (p=0.004 n=7)
Fio/operation.write/blockSize.4K/filesystem.bindfs-4             2.008m ±  0%   2.009m ± 25%       ~ (p=0.805 n=7)
Fio/operation.write/blockSize.64K/filesystem.bindfs-4            1.507m ±  0%   1.507m ±  0%       ~ (p=0.318 n=7)
Fio/operation.write/blockSize.1024K/filesystem.bindfs-4          1.507m ±  0%   1.507m ±  0%       ~ (p=1.000 n=7)
Fio/operation.write/blockSize.4K/filesystem.tmpfs-4              2.510m ±  0%   2.509m ±  0%       ~ (p=0.805 n=7)
Fio/operation.write/blockSize.64K/filesystem.tmpfs-4             2.009m ±  0%   2.009m ±  0%       ~ (p=0.805 n=7)
Fio/operation.write/blockSize.1024K/filesystem.tmpfs-4           2.009m ±  0%   2.008m ±  0%       ~ (p=0.097 n=7)
Fio/operation.write/blockSize.4K/filesystem.rootfs-4             3.010m ± 17%   2.510m ±  0%       ~ (p=0.053 n=7)
Fio/operation.write/blockSize.64K/filesystem.rootfs-4            2.009m ± 25%   2.008m ±  0%       ~ (p=1.000 n=7)
Fio/operation.write/blockSize.1024K/filesystem.rootfs-4          2.009m ±  0%   2.008m ±  0%       ~ (p=0.259 n=7)
RubySpecTest/page_cache.clean/filesystem.bindfs-4                 63.15 ±  2%    61.65 ±  2%  -2.38% (p=0.011 n=7)
geomean                                                          34.15m         33.48m        -1.94%

                                                        │    /tmp/benchout.runsc     │              /tmp/benchout.runsc-cl1              │
                                                        │ bandwidth.bytes_per_second │ bandwidth.bytes_per_second  vs base               │
Fio/operation.write/blockSize.4K/filesystem.bindfs-4                    939.6M ±  2%                 944.7M ±  4%        ~ (p=0.737 n=7)
Fio/operation.write/blockSize.64K/filesystem.bindfs-4                   2.937G ±  5%                 2.873G ±  2%        ~ (p=0.216 n=7)
Fio/operation.write/blockSize.1024K/filesystem.bindfs-4                 3.139G ±  7%                 3.112G ±  6%        ~ (p=0.879 n=7)
Fio/operation.write/blockSize.4K/filesystem.tmpfs-4                     1.355G ±  3%                 1.382G ±  5%        ~ (p=0.090 n=7)
Fio/operation.write/blockSize.64K/filesystem.tmpfs-4                    3.913G ± 10%                 3.785G ± 10%        ~ (p=0.779 n=7)
Fio/operation.write/blockSize.1024K/filesystem.tmpfs-4                  3.692G ± 11%                 3.855G ±  6%        ~ (p=1.000 n=7)
Fio/operation.write/blockSize.4K/filesystem.rootfs-4                    788.4M ±  2%                 628.6M ±  1%  -20.26% (p=0.001 n=7)
Fio/operation.write/blockSize.64K/filesystem.rootfs-4                  1346.1M ±  5%                 940.4M ±  6%  -30.13% (p=0.001 n=7)
Fio/operation.write/blockSize.1024K/filesystem.rootfs-4                 1.646G ±  5%                 1.173G ±  3%  -28.75% (p=0.001 n=7)
geomean                                                                 1.882G                       1.699G         -9.72%

                                                        │  /tmp/benchout.runsc  │           /tmp/benchout.runsc-cl1            │
                                                        │ io_ops.ops_per_second │ io_ops.ops_per_second  vs base               │
Fio/operation.write/blockSize.4K/filesystem.bindfs-4               229.4k ±  2%            230.6k ±  4%        ~ (p=0.737 n=7)
Fio/operation.write/blockSize.64K/filesystem.bindfs-4              44.82k ±  5%            43.84k ±  2%        ~ (p=0.216 n=7)
Fio/operation.write/blockSize.1024K/filesystem.bindfs-4            2.994k ±  7%            2.967k ±  6%        ~ (p=0.879 n=7)
Fio/operation.write/blockSize.4K/filesystem.tmpfs-4                330.7k ±  3%            337.3k ±  5%        ~ (p=0.090 n=7)
Fio/operation.write/blockSize.64K/filesystem.tmpfs-4               59.70k ± 10%            57.76k ± 10%        ~ (p=0.779 n=7)
Fio/operation.write/blockSize.1024K/filesystem.tmpfs-4             3.521k ± 11%            3.676k ±  6%        ~ (p=1.000 n=7)
Fio/operation.write/blockSize.4K/filesystem.rootfs-4               192.5k ±  2%            153.5k ±  1%  -20.26% (p=0.001 n=7)
Fio/operation.write/blockSize.64K/filesystem.rootfs-4              20.54k ±  5%            14.35k ±  6%  -30.13% (p=0.001 n=7)
Fio/operation.write/blockSize.1024K/filesystem.rootfs-4            1.570k ±  5%            1.119k ±  3%  -28.73% (p=0.001 n=7)
geomean                                                            28.72k                  25.93k         -9.72%

                                                  │ /tmp/benchout.runsc │     /tmp/benchout.runsc-cl1      │
                                                  │      load.sec       │  load.sec   vs base              │
RubySpecTest/page_cache.clean/filesystem.bindfs-4            9.430 ± 7%   9.150 ± 7%  -2.97% (p=0.026 n=7)
```

The broader filesystem benchmarks (ABSL, gRPC, Ruby) all improved with this
change. Improvement is ~2-3% reduction in absolute time to run these
workloads. However, the FIO microbenchmark is a little more complicated.
The latency metric shows improvement (for blockSize=4K & filesystem=rootfs).
It is stable for other blockSize values. But FIO bandwidth seems to have
regressed. We think this is an accounting gimmick. We suspect that sec/op
includes the app fallocate() and bandwidth does not. Since this change moves
ext4_mpage_readpages() from app fallocate() to the page faults that occur
during app write(), bandwidth appears to have regressed.

Given that the broader filesystem benchmarks look good and that this is more
consistent with Linux (app fallocate() now only commits disk space rather than
also materializing memory pages), this change should be an improvement.

PiperOrigin-RevId: 542331863
2023-06-21 12:36:14 -07:00
Ayush RanjanandgVisor bot 1b7a4e2a05 Prepare to make root overlay the default.
We plan on making --overlay2=root:self the default for runsc. That will be a
risky change which might need rollbacks. This change is in preparation for
that. We manually set --overlay2=none in places where we don't want the
overlay configuration to impact. This change should be a noop. The intention
for this change is to make the risky change very small and limited to just
flipping a flag.

PiperOrigin-RevId: 513930702
2023-03-04 00:46:47 -08:00
Adin ScannellandgVisor bot 1ceb814544 Add default_applicable_licenses rules to packages.
PiperOrigin-RevId: 513581243
2023-03-02 10:50:04 -08:00
Jamie LiuandgVisor bot 0721ca2fe3 Make hostarch.Page/HugePage/CacheLine* functions generic and canonical.
Also add the missing HugePage equivalents of Page functions.

The fix for golang/go#56280, which first appears in Go 1.20, is needed to
prevent this CL from regressing performance:

Before fix:
```
TEXT gvisor/pkg/sentry/mm/mm.(*MemoryManager).SetNumaPolicy(SB) gvisor/pkg/sentry/mm/syscalls.go
  ...
  addr.go:75            0x7f000054e35e          488d053bc2b100          LEAQ gvisor/pkg/hostarch/hostarch..dict.IsPageAligned[gvisor/pkg/hostarch/hostarch.Addr](SB), AX
  addr.go:75            0x7f000054e365          e8961cc4ff              CALL gvisor/pkg/hostarch/hostarch.IsPageAligned[go.shape.uintptr](SB)
```

After fix:
```
TEXT gvisor/pkg/sentry/mm/mm.(*MemoryManager).SetNumaPolicy(SB) gvisor/pkg/sentry/mm/syscalls.go
  ...
  addr.go:75            0x7f00004da61a          90                      NOPL
  addr.go:75            0x7f00004da61b          0f1f440000              NOPL 0(AX)(AX*1)
  sizes_util.go:57      0x7f00004da620          48f7c3ff0f0000          TESTQ $0xfff, BX
  syscalls.go:1021      0x7f00004da627          0f855f010000            JNE 0x7f00004da78c
```

PiperOrigin-RevId: 507608080
2023-02-06 16:09:19 -08:00
Ayush RanjanandgVisor bot c16cbd3f6e Create separate overlay filestore for subcontainers.
Earlier we were using the same filestore for all containers in the
sandbox. This was based on the assumption that all containers in a
k8s pod have the same runsc configuration. We get rid of our
dependence on that assumption. Subcontainers can independently use
overlay configurations.

This is needed because in k8s, different ephemeral storage limits can
be enforced on containers in the same pod. This behavior is required
to get limit enforcement to work correctly on a per container basis.

This change also moves the ownership of overlay filestore to tmpfs.
Now that each container has its own overlay-backing MemoryFile, it will
be much harder to manage lifecycle of these MemoryFiles as containers
are created and destroyed.

Instead, move ownership to tmpfs mount. When the overlay upper layer tmpfs
is unmounted, it will destroy the MemoryFile.

We also extract out IMA work around logic from NewMemoryFile() and perform
it outside the sandbox. This is done because now tmpfs is creating the
MemoryFile. By then the sandbox is already running with seccomp filters
installed that do not allow certain mmap(2) calls that the said work around
uses. We hit the same issue for subcontainers. When they are started,
the sandbox is already running with seccomp filters. Added a new
MemoryFileOpts field for this which defaults to old behavior.

PiperOrigin-RevId: 505892085
2023-01-31 09:42:00 -08:00
Ayush RanjanandgVisor bot aff351f684 Handle terminal symlinks correctly in OpenAt() implementations.
All filesystems except tmpfs had the same bug regarding handling symlinks
in the terminal path component. When they tried to open(2) such a symlink
file, these implementations called into stepLocked() with
Then these implementations also called vfs.HandleSymlink() which would
also advance rp (when rp.Done() was already true).

This was tripping the invariants checks in sentry/vfs/debug.go.

PiperOrigin-RevId: 504098263
2023-01-23 15:35:15 -08:00
Ayush RanjanandgVisor bot a58df80df3 Make directory open flags checks consistent in tmpfs and kernfs.
Overlayfs and goferfs seem to be up to date.
Updated syscall tests.

PiperOrigin-RevId: 503521750
2023-01-20 14:22:38 -08:00
Ayush RanjanandgVisor bot f14b4bb3ca Fix tmpfs accounting bug in Allocate().
FileRangeSet.Fill() can fill in gaps partially and fail. Earlier, we were
over-correcting on failure by unaccounting everything that was reserved for the
operation. But that can lead to accounting errors in case of partial Fill()
failures. In such a case, when the file is later deleted, we would again
unaccount all the pages that were allocated via the partial Fill(), which will
causes accounting to go negative and sentry will panic.

Also did some refactoring for clearer code.

PiperOrigin-RevId: 493935801
2022-12-08 10:41:25 -08:00
Ayush RanjanandgVisor bot 5c59bdb8f1 Set default tmpfs size option to the large value advertised in statfs(2).
We were already returning this large size via statfs(2). But there was no
accounting being done against it. Even after page allocations, there was no
change in values returned by statfs(2) about number of pages free.

PHP runtime test ext/standard/tests/file/disk_free_space_basic.phpt and Python
runtime test test_shutil tests for this. This change is required to get these
tests to pass with overlayfs.

PiperOrigin-RevId: 493903408
2022-12-08 08:35:57 -08:00
Ayush RanjanandgVisor bot c6faaea7dc Make tmpfs size accounting lock-free by using atomics.
This is likely to be a speed-up in the common case when contention between
threads is low. In such situations, generally atomic operations are faster.

PiperOrigin-RevId: 493731992
2022-12-07 15:58:10 -08:00
Lucas ManningandgVisor bot 99cc6c2dea Display the proper root path in mountinfo.
PiperOrigin-RevId: 492323370
2022-12-01 16:05:00 -08:00
Ayush RanjanandgVisor bot 175db901ca Replace pkg/refs with pkg/refsvfs2.
All VFS1 only bits have been deleted.

Updates #1624

PiperOrigin-RevId: 492273183
2022-12-01 12:46:11 -08:00
d7b57d2fd3 Add overlay2 flag in runsc.
--overlay2 flag supersedes --overlay flag. It allows more granular
configuration for overlayfs in runsc. It does so in two ways:

1. Allows to apply overlay on all mounts or only the root mount.
   --overlay applies overlay to all mounts.
2. Allows to specify if overlay's upper layer should be backed by
   container memory or disk. --overlay always used container memory.

Allowing tmpfs to be backed by a file on disk prevents the container
memory from bloating up. Note that the tmpfs filesystem tree will
still be stored in sentry memory.

Using overlay on the root filesystem, helps avoid expensive
communication with the gofer process. The root filesystem of the
container is not preserved across container lifecycle. So we don't
need to keep updating the host filesystem, which will anyways be
destroyed once the container is destroyed. It is wasted effort.
Instead we keep all the changes to the root filesystem in tmpfs which
is directly accessible by the sentry.

The host file is created as an unnamed file using O_TMPFILE. Support
has been added for sub-containers too. Save/restore support is
still lacking.

Co-authored-by: Andrei Vagin <avagin@gmail.com>
PiperOrigin-RevId: 491988485
2022-11-30 12:29:14 -08:00
Ayush RanjanandgVisor bot 1fa3c06f1e Delete VFS1 completely.
- Delete pkg/sentry/fs/*.
- Move pkg/sentry/fs/fsutil out of VFS1 directory and remove VFS1 components.
- Remove remaining unused references to VFS1 from remaining codebase.
- Rename/refactor code to avoid even referencing VFS2, unless necessary.
- Rewrite VFS1-only tests to VFS2.

Updates #1624

PiperOrigin-RevId: 490064269
2022-11-21 13:57:52 -08:00
Ayush RanjanandgVisor bot 7eeeb796f8 Delete VFS1 filesystem implementations.
Updates #1624

PiperOrigin-RevId: 488986080
2022-11-16 11:05:10 -08:00
Andrei VaginandgVisor bot 374e716c7c AddSeals has to take the write lock to modify seals
Reported-by: syzbot+355993d42d99b011ea73@syzkaller.appspotmail.com
Reported-by: syzbot+f758f7983773e0a60c7b@syzkaller.appspotmail.com
PiperOrigin-RevId: 488764524
2022-11-15 14:49:20 -08:00