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:
Dean Deng
2020-06-24 19:22:12 -07:00
committed by gVisor bot
parent ac6f7b600b
commit b5e814445a
7 changed files with 117 additions and 10 deletions
@@ -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
}
+59 -1
View File
@@ -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
+1 -1
View File
@@ -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)
}
}
+2
View File
@@ -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(
+47 -2
View File
@@ -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.