mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Fix procfs bugs in vfs2.
- Support writing on proc/[pid]/{uid,gid}map
- Return EIO for writing to static files.
Updates #2923.
PiperOrigin-RevId: 318188503
This commit is contained in:
@@ -101,12 +101,12 @@ func (fd *DynamicBytesFD) Seek(ctx context.Context, offset int64, whence int32)
|
||||
return fd.DynamicBytesFileDescriptionImpl.Seek(ctx, offset, whence)
|
||||
}
|
||||
|
||||
// Read implmenets vfs.FileDescriptionImpl.Read.
|
||||
// Read implements vfs.FileDescriptionImpl.Read.
|
||||
func (fd *DynamicBytesFD) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) {
|
||||
return fd.DynamicBytesFileDescriptionImpl.Read(ctx, dst, opts)
|
||||
}
|
||||
|
||||
// PRead implmenets vfs.FileDescriptionImpl.PRead.
|
||||
// PRead implements vfs.FileDescriptionImpl.PRead.
|
||||
func (fd *DynamicBytesFD) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) {
|
||||
return fd.DynamicBytesFileDescriptionImpl.PRead(ctx, dst, offset, opts)
|
||||
}
|
||||
|
||||
@@ -293,6 +293,8 @@ func (a *InodeAttrs) SetStat(ctx context.Context, fs *vfs.Filesystem, creds *aut
|
||||
// inode numbers are immutable after node creation.
|
||||
|
||||
// TODO(gvisor.dev/issue/1193): Implement other stat fields like timestamps.
|
||||
// Also, STATX_SIZE will need some special handling, because read-only static
|
||||
// files should return EIO for truncate operations.
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -35,6 +35,10 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
)
|
||||
|
||||
// "There is an (arbitrary) limit on the number of lines in the file. As at
|
||||
// Linux 3.18, the limit is five lines." - user_namespaces(7)
|
||||
const maxIDMapLines = 5
|
||||
|
||||
// mm gets the kernel task's MemoryManager. No additional reference is taken on
|
||||
// mm here. This is safe because MemoryManager.destroy is required to leave the
|
||||
// MemoryManager in a state where it's still usable as a DynamicBytesSource.
|
||||
@@ -283,7 +287,8 @@ func (d *commData) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// idMapData implements vfs.DynamicBytesSource for /proc/[pid]/{gid_map|uid_map}.
|
||||
// idMapData implements vfs.WritableDynamicBytesSource for
|
||||
// /proc/[pid]/{gid_map|uid_map}.
|
||||
//
|
||||
// +stateify savable
|
||||
type idMapData struct {
|
||||
@@ -309,6 +314,59 @@ func (d *idMapData) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *idMapData) Write(ctx context.Context, src usermem.IOSequence, offset int64) (int64, error) {
|
||||
// "In addition, the number of bytes written to the file must be less than
|
||||
// the system page size, and the write must be performed at the start of
|
||||
// the file ..." - user_namespaces(7)
|
||||
srclen := src.NumBytes()
|
||||
if srclen >= usermem.PageSize || offset != 0 {
|
||||
return 0, syserror.EINVAL
|
||||
}
|
||||
b := make([]byte, srclen)
|
||||
if _, err := src.CopyIn(ctx, b); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Truncate from the first NULL byte.
|
||||
var nul int64
|
||||
nul = int64(bytes.IndexByte(b, 0))
|
||||
if nul == -1 {
|
||||
nul = srclen
|
||||
}
|
||||
b = b[:nul]
|
||||
// Remove the last \n.
|
||||
if nul >= 1 && b[nul-1] == '\n' {
|
||||
b = b[:nul-1]
|
||||
}
|
||||
lines := bytes.SplitN(b, []byte("\n"), maxIDMapLines+1)
|
||||
if len(lines) > maxIDMapLines {
|
||||
return 0, syserror.EINVAL
|
||||
}
|
||||
|
||||
entries := make([]auth.IDMapEntry, len(lines))
|
||||
for i, l := range lines {
|
||||
var e auth.IDMapEntry
|
||||
_, err := fmt.Sscan(string(l), &e.FirstID, &e.FirstParentID, &e.Length)
|
||||
if err != nil {
|
||||
return 0, syserror.EINVAL
|
||||
}
|
||||
entries[i] = e
|
||||
}
|
||||
var err error
|
||||
if d.gids {
|
||||
err = d.task.UserNamespace().SetGIDMap(ctx, entries)
|
||||
} else {
|
||||
err = d.task.UserNamespace().SetUIDMap(ctx, entries)
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// On success, Linux's kernel/user_namespace.c:map_write() always returns
|
||||
// count, even if fewer bytes were used.
|
||||
return int64(srclen), nil
|
||||
}
|
||||
|
||||
// mapsData implements vfs.DynamicBytesSource for /proc/[pid]/maps.
|
||||
//
|
||||
// +stateify savable
|
||||
|
||||
@@ -327,7 +327,7 @@ func (fd *DynamicBytesFileDescriptionImpl) pwriteLocked(ctx context.Context, src
|
||||
|
||||
writable, ok := fd.data.(WritableDynamicBytesSource)
|
||||
if !ok {
|
||||
return 0, syserror.EINVAL
|
||||
return 0, syserror.EIO
|
||||
}
|
||||
n, err := writable.Write(ctx, src, offset)
|
||||
if err != nil {
|
||||
|
||||
@@ -155,11 +155,11 @@ func TestGenCountFD(t *testing.T) {
|
||||
}
|
||||
|
||||
// Write and PWrite fails.
|
||||
if _, err := fd.Write(ctx, ioseq, WriteOptions{}); err != syserror.EINVAL {
|
||||
t.Errorf("Write: got err %v, wanted %v", err, syserror.EINVAL)
|
||||
if _, err := fd.Write(ctx, ioseq, WriteOptions{}); err != syserror.EIO {
|
||||
t.Errorf("Write: got err %v, wanted %v", err, syserror.EIO)
|
||||
}
|
||||
if _, err := fd.PWrite(ctx, ioseq, 0, WriteOptions{}); err != syserror.EINVAL {
|
||||
t.Errorf("Write: got err %v, wanted %v", err, syserror.EINVAL)
|
||||
if _, err := fd.PWrite(ctx, ioseq, 0, WriteOptions{}); err != syserror.EIO {
|
||||
t.Errorf("Write: got err %v, wanted %v", err, syserror.EIO)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -479,6 +479,7 @@ syscall_test(
|
||||
syscall_test(
|
||||
size = "medium",
|
||||
test = "//test/syscalls/linux:proc_test",
|
||||
vfs2 = "True",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
@@ -498,6 +499,7 @@ syscall_test(
|
||||
|
||||
syscall_test(
|
||||
test = "//test/syscalls/linux:proc_pid_uid_gid_map_test",
|
||||
vfs2 = "True",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
|
||||
@@ -754,8 +754,53 @@ TEST(ProcCpuinfo, RequiredFieldsArePresent) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ProcCpuinfo, DeniesWrite) {
|
||||
EXPECT_THAT(open("/proc/cpuinfo", O_WRONLY), SyscallFailsWithErrno(EACCES));
|
||||
TEST(ProcCpuinfo, DeniesWriteNonRoot) {
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_FOWNER)));
|
||||
|
||||
// Do setuid in a separate thread so that after finishing this test, the
|
||||
// process can still open files the test harness created before starting this
|
||||
// test. Otherwise, the files are created by root (UID before the test), but
|
||||
// cannot be opened by the `uid` set below after the test. After calling
|
||||
// setuid(non-zero-UID), there is no way to get root privileges back.
|
||||
ScopedThread([&] {
|
||||
// Use syscall instead of glibc setuid wrapper because we want this setuid
|
||||
// call to only apply to this task. POSIX threads, however, require that all
|
||||
// threads have the same UIDs, so using the setuid wrapper sets all threads'
|
||||
// real UID.
|
||||
// Also drops capabilities.
|
||||
constexpr int kNobody = 65534;
|
||||
EXPECT_THAT(syscall(SYS_setuid, kNobody), SyscallSucceeds());
|
||||
EXPECT_THAT(open("/proc/cpuinfo", O_WRONLY), SyscallFailsWithErrno(EACCES));
|
||||
// TODO(gvisor.dev/issue/1193): Properly support setting size attributes in
|
||||
// kernfs.
|
||||
if (!IsRunningOnGvisor() || IsRunningWithVFS1()) {
|
||||
EXPECT_THAT(truncate("/proc/cpuinfo", 123),
|
||||
SyscallFailsWithErrno(EACCES));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// With root privileges, it is possible to open /proc/cpuinfo with write mode,
|
||||
// but all write operations will return EIO.
|
||||
TEST(ProcCpuinfo, DeniesWriteRoot) {
|
||||
// VFS1 does not behave differently for root/non-root.
|
||||
SKIP_IF(IsRunningWithVFS1());
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_FOWNER)));
|
||||
|
||||
int fd;
|
||||
EXPECT_THAT(fd = open("/proc/cpuinfo", O_WRONLY), SyscallSucceeds());
|
||||
if (fd > 0) {
|
||||
EXPECT_THAT(write(fd, "x", 1), SyscallFailsWithErrno(EIO));
|
||||
EXPECT_THAT(pwrite(fd, "x", 1, 123), SyscallFailsWithErrno(EIO));
|
||||
}
|
||||
// TODO(gvisor.dev/issue/1193): Properly support setting size attributes in
|
||||
// kernfs.
|
||||
if (!IsRunningOnGvisor() || IsRunningWithVFS1()) {
|
||||
if (fd > 0) {
|
||||
EXPECT_THAT(ftruncate(fd, 123), SyscallFailsWithErrno(EIO));
|
||||
}
|
||||
EXPECT_THAT(truncate("/proc/cpuinfo", 123), SyscallFailsWithErrno(EIO));
|
||||
}
|
||||
}
|
||||
|
||||
// Sanity checks that uptime is present.
|
||||
|
||||
Reference in New Issue
Block a user