summaryrefslogtreecommitdiffhomepage
path: root/pkg
diff options
context:
space:
mode:
authorBrian Geffon <bgeffon@google.com>2018-12-04 14:31:08 -0800
committerShentubot <shentubot@google.com>2018-12-04 14:32:03 -0800
commit82719be42e636f86780d21b01e10ecb2c9a25e53 (patch)
tree1c635cae30683e3cdc13a497cf529063ed7f56dc /pkg
parentadafc08d7cee594ea94abefbedf67ea315922550 (diff)
Max link traversals should be for an entire path.
The number of symbolic links that are allowed to be followed are for a full path and not just a chain of symbolic links. PiperOrigin-RevId: 224047321 Change-Id: I5e3c4caf66a93c17eeddcc7f046d1e8bb9434a40
Diffstat (limited to 'pkg')
-rw-r--r--pkg/sentry/fs/copy_up_test.go3
-rw-r--r--pkg/sentry/fs/host/fs.go3
-rw-r--r--pkg/sentry/fs/host/fs_test.go3
-rw-r--r--pkg/sentry/fs/inode_overlay_test.go3
-rw-r--r--pkg/sentry/fs/mount_test.go10
-rw-r--r--pkg/sentry/fs/mounts.go22
-rw-r--r--pkg/sentry/fs/mounts_test.go6
-rw-r--r--pkg/sentry/fs/ramfs/tree_test.go3
-rw-r--r--pkg/sentry/kernel/kernel.go6
-rw-r--r--pkg/sentry/kernel/task_context.go2
-rw-r--r--pkg/sentry/loader/elf.go2
-rw-r--r--pkg/sentry/loader/loader.go10
-rw-r--r--pkg/sentry/socket/unix/unix.go6
-rw-r--r--pkg/sentry/syscalls/linux/sys_file.go11
-rw-r--r--pkg/sentry/syscalls/linux/sys_thread.go3
15 files changed, 57 insertions, 36 deletions
diff --git a/pkg/sentry/fs/copy_up_test.go b/pkg/sentry/fs/copy_up_test.go
index 64f030f72..fcba14ed4 100644
--- a/pkg/sentry/fs/copy_up_test.go
+++ b/pkg/sentry/fs/copy_up_test.go
@@ -166,7 +166,8 @@ func makeOverlayTestFiles(t *testing.T) []*overlayTestFile {
// Walk to all of the files in the overlay, open them readable.
for _, f := range files {
- d, err := mns.FindInode(ctx, mns.Root(), mns.Root(), f.name, 0)
+ maxTraversals := uint(0)
+ d, err := mns.FindInode(ctx, mns.Root(), mns.Root(), f.name, &maxTraversals)
if err != nil {
t.Fatalf("failed to find %q: %v", f.name, err)
}
diff --git a/pkg/sentry/fs/host/fs.go b/pkg/sentry/fs/host/fs.go
index fec890964..54cbb94f9 100644
--- a/pkg/sentry/fs/host/fs.go
+++ b/pkg/sentry/fs/host/fs.go
@@ -170,7 +170,8 @@ func installWhitelist(ctx context.Context, m *fs.MountNamespace, paths []string)
current := paths[i][:j]
// Lookup the given component in the tree.
- d, err := m.FindLink(ctx, root, nil, current, maxTraversals)
+ remainingTraversals := uint(maxTraversals)
+ d, err := m.FindLink(ctx, root, nil, current, &remainingTraversals)
if err != nil {
log.Warningf("populate failed for %q: %v", current, err)
continue
diff --git a/pkg/sentry/fs/host/fs_test.go b/pkg/sentry/fs/host/fs_test.go
index e69559aac..44db61ecd 100644
--- a/pkg/sentry/fs/host/fs_test.go
+++ b/pkg/sentry/fs/host/fs_test.go
@@ -150,7 +150,8 @@ func allPaths(ctx context.Context, t *testing.T, m *fs.MountNamespace, base stri
root := m.Root()
defer root.DecRef()
- d, err := m.FindLink(ctx, root, nil, base, 1)
+ maxTraversals := uint(1)
+ d, err := m.FindLink(ctx, root, nil, base, &maxTraversals)
if err != nil {
t.Logf("FindLink failed for %q", base)
return paths, err
diff --git a/pkg/sentry/fs/inode_overlay_test.go b/pkg/sentry/fs/inode_overlay_test.go
index bba20da14..acdb2b4f8 100644
--- a/pkg/sentry/fs/inode_overlay_test.go
+++ b/pkg/sentry/fs/inode_overlay_test.go
@@ -324,7 +324,8 @@ func TestCacheFlush(t *testing.T) {
for _, fileName := range []string{upperFileName, lowerFileName} {
// Walk to the file.
- dirent, err := mns.FindInode(ctx, root, nil, fileName, 0)
+ maxTraversals := uint(0)
+ dirent, err := mns.FindInode(ctx, root, nil, fileName, &maxTraversals)
if err != nil {
t.Fatalf("FindInode(%q) failed: %v", fileName, err)
}
diff --git a/pkg/sentry/fs/mount_test.go b/pkg/sentry/fs/mount_test.go
index a1c9f4f79..269d6b9da 100644
--- a/pkg/sentry/fs/mount_test.go
+++ b/pkg/sentry/fs/mount_test.go
@@ -115,8 +115,10 @@ func TestMountSourceParentChildRelationship(t *testing.T) {
"/waldo",
}
+ var maxTraversals uint
for _, p := range paths {
- d, err := mm.FindLink(ctx, rootDirent, nil, p, 0)
+ maxTraversals = 0
+ d, err := mm.FindLink(ctx, rootDirent, nil, p, &maxTraversals)
if err != nil {
t.Fatalf("could not find path %q in mount manager: %v", p, err)
}
@@ -164,7 +166,8 @@ func TestMountSourceParentChildRelationship(t *testing.T) {
}
// "foo" mount should have two children: /foo/bar, and /foo/qux.
- d, err := mm.FindLink(ctx, rootDirent, nil, "/foo", 0)
+ maxTraversals = 0
+ d, err := mm.FindLink(ctx, rootDirent, nil, "/foo", &maxTraversals)
if err != nil {
t.Fatalf("could not find path %q in mount manager: %v", "/foo", err)
}
@@ -185,7 +188,8 @@ func TestMountSourceParentChildRelationship(t *testing.T) {
}
// "waldo" mount should have no submounts or children.
- waldo, err := mm.FindLink(ctx, rootDirent, nil, "/waldo", 0)
+ maxTraversals = 0
+ waldo, err := mm.FindLink(ctx, rootDirent, nil, "/waldo", &maxTraversals)
if err != nil {
t.Fatalf("could not find path %q in mount manager: %v", "/waldo", err)
}
diff --git a/pkg/sentry/fs/mounts.go b/pkg/sentry/fs/mounts.go
index 7c5348cce..f6f7be0aa 100644
--- a/pkg/sentry/fs/mounts.go
+++ b/pkg/sentry/fs/mounts.go
@@ -350,7 +350,7 @@ func (mns *MountNamespace) Unmount(ctx context.Context, node *Dirent, detachOnly
//
// Precondition: root must be non-nil.
// Precondition: the path must be non-empty.
-func (mns *MountNamespace) FindLink(ctx context.Context, root, wd *Dirent, path string, maxTraversals uint) (*Dirent, error) {
+func (mns *MountNamespace) FindLink(ctx context.Context, root, wd *Dirent, path string, remainingTraversals *uint) (*Dirent, error) {
if root == nil {
panic("MountNamespace.FindLink: root must not be nil")
}
@@ -419,7 +419,7 @@ func (mns *MountNamespace) FindLink(ctx context.Context, root, wd *Dirent, path
//
// See resolve for reference semantics; on err next
// will have one dropped.
- current, err = mns.resolve(ctx, root, next, maxTraversals)
+ current, err = mns.resolve(ctx, root, next, remainingTraversals)
if err != nil {
return nil, err
}
@@ -439,15 +439,15 @@ func (mns *MountNamespace) FindLink(ctx context.Context, root, wd *Dirent, path
// FindInode is identical to FindLink except the return value is resolved.
//
//go:nosplit
-func (mns *MountNamespace) FindInode(ctx context.Context, root, wd *Dirent, path string, maxTraversals uint) (*Dirent, error) {
- d, err := mns.FindLink(ctx, root, wd, path, maxTraversals)
+func (mns *MountNamespace) FindInode(ctx context.Context, root, wd *Dirent, path string, remainingTraversals *uint) (*Dirent, error) {
+ d, err := mns.FindLink(ctx, root, wd, path, remainingTraversals)
if err != nil {
return nil, err
}
// See resolve for reference semantics; on err d will have the
// reference dropped.
- return mns.resolve(ctx, root, d, maxTraversals)
+ return mns.resolve(ctx, root, d, remainingTraversals)
}
// resolve resolves the given link.
@@ -458,14 +458,14 @@ func (mns *MountNamespace) FindInode(ctx context.Context, root, wd *Dirent, path
// If not successful, a reference is _also_ dropped on the node and an error
// returned. This is for convenience in using resolve directly as a return
// value.
-func (mns *MountNamespace) resolve(ctx context.Context, root, node *Dirent, maxTraversals uint) (*Dirent, error) {
+func (mns *MountNamespace) resolve(ctx context.Context, root, node *Dirent, remainingTraversals *uint) (*Dirent, error) {
// Resolve the path.
target, err := node.Inode.Getlink(ctx)
switch err {
case nil:
// Make sure we didn't exhaust the traversal budget.
- if maxTraversals == 0 {
+ if *remainingTraversals == 0 {
target.DecRef()
return nil, syscall.ELOOP
}
@@ -481,7 +481,7 @@ func (mns *MountNamespace) resolve(ctx context.Context, root, node *Dirent, maxT
defer node.DecRef() // See above.
// First, check if we should traverse.
- if maxTraversals == 0 {
+ if *remainingTraversals == 0 {
return nil, syscall.ELOOP
}
@@ -492,7 +492,8 @@ func (mns *MountNamespace) resolve(ctx context.Context, root, node *Dirent, maxT
}
// Find the node; we resolve relative to the current symlink's parent.
- d, err := mns.FindInode(ctx, root, node.parent, targetPath, maxTraversals-1)
+ *remainingTraversals--
+ d, err := mns.FindInode(ctx, root, node.parent, targetPath, remainingTraversals)
if err != nil {
return nil, err
}
@@ -544,7 +545,8 @@ func (mns *MountNamespace) ResolveExecutablePath(ctx context.Context, wd, name s
defer root.DecRef()
for _, p := range paths {
binPath := path.Join(p, name)
- d, err := mns.FindInode(ctx, root, nil, binPath, linux.MaxSymlinkTraversals)
+ traversals := uint(linux.MaxSymlinkTraversals)
+ d, err := mns.FindInode(ctx, root, nil, binPath, &traversals)
if err == syserror.ENOENT || err == syserror.EACCES {
// Didn't find it here.
continue
diff --git a/pkg/sentry/fs/mounts_test.go b/pkg/sentry/fs/mounts_test.go
index cc7c32c9b..2f7a1710f 100644
--- a/pkg/sentry/fs/mounts_test.go
+++ b/pkg/sentry/fs/mounts_test.go
@@ -77,7 +77,8 @@ func TestFindLink(t *testing.T) {
{"bar", foo, "/foo/bar"},
} {
wdPath, _ := tc.wd.FullName(root)
- if d, err := mm.FindLink(ctx, root, tc.wd, tc.findPath, 0); err != nil {
+ maxTraversals := uint(0)
+ if d, err := mm.FindLink(ctx, root, tc.wd, tc.findPath, &maxTraversals); err != nil {
t.Errorf("FindLink(%q, wd=%q) failed: %v", tc.findPath, wdPath, err)
} else if got, _ := d.FullName(root); got != tc.wantPath {
t.Errorf("FindLink(%q, wd=%q) got dirent %q, want %q", tc.findPath, wdPath, got, tc.wantPath)
@@ -95,7 +96,8 @@ func TestFindLink(t *testing.T) {
{"foo", foo},
} {
wdPath, _ := tc.wd.FullName(root)
- if _, err := mm.FindLink(ctx, root, tc.wd, tc.findPath, 0); err == nil {
+ maxTraversals := uint(0)
+ if _, err := mm.FindLink(ctx, root, tc.wd, tc.findPath, &maxTraversals); err == nil {
t.Errorf("FindLink(%q, wd=%q) did not return error", tc.findPath, wdPath)
}
}
diff --git a/pkg/sentry/fs/ramfs/tree_test.go b/pkg/sentry/fs/ramfs/tree_test.go
index d5567d9e1..54df2143c 100644
--- a/pkg/sentry/fs/ramfs/tree_test.go
+++ b/pkg/sentry/fs/ramfs/tree_test.go
@@ -70,7 +70,8 @@ func TestMakeDirectoryTree(t *testing.T) {
defer mm.DecRef()
for _, p := range test.subdirs {
- if _, err := mm.FindInode(ctx, root, nil, p, 0); err != nil {
+ maxTraversals := uint(0)
+ if _, err := mm.FindInode(ctx, root, nil, p, &maxTraversals); err != nil {
t.Errorf("%s: failed to find node %s: %v", test.name, p, err)
break
}
diff --git a/pkg/sentry/kernel/kernel.go b/pkg/sentry/kernel/kernel.go
index 17425e656..cb61e27f1 100644
--- a/pkg/sentry/kernel/kernel.go
+++ b/pkg/sentry/kernel/kernel.go
@@ -634,10 +634,11 @@ func (k *Kernel) CreateProcess(args CreateProcessArgs) (*ThreadGroup, ThreadID,
args.Root = nil
// Grab the working directory.
+ remainingTraversals := uint(args.MaxSymlinkTraversals)
wd := root // Default.
if args.WorkingDirectory != "" {
var err error
- wd, err = k.mounts.FindInode(ctx, root, nil, args.WorkingDirectory, args.MaxSymlinkTraversals)
+ wd, err = k.mounts.FindInode(ctx, root, nil, args.WorkingDirectory, &remainingTraversals)
if err != nil {
return nil, 0, fmt.Errorf("failed to find initial working directory %q: %v", args.WorkingDirectory, err)
}
@@ -656,7 +657,8 @@ func (k *Kernel) CreateProcess(args CreateProcessArgs) (*ThreadGroup, ThreadID,
}
// Create a fresh task context.
- tc, err := k.LoadTaskImage(ctx, k.mounts, root, wd, args.MaxSymlinkTraversals, args.Filename, args.Argv, args.Envv, k.featureSet)
+ remainingTraversals = uint(args.MaxSymlinkTraversals)
+ tc, err := k.LoadTaskImage(ctx, k.mounts, root, wd, &remainingTraversals, args.Filename, args.Argv, args.Envv, k.featureSet)
if err != nil {
return nil, 0, err
}
diff --git a/pkg/sentry/kernel/task_context.go b/pkg/sentry/kernel/task_context.go
index 45b8d2b04..aaff309f0 100644
--- a/pkg/sentry/kernel/task_context.go
+++ b/pkg/sentry/kernel/task_context.go
@@ -142,7 +142,7 @@ func (t *Task) Stack() *arch.Stack {
// * argv: Binary argv
// * envv: Binary envv
// * fs: Binary FeatureSet
-func (k *Kernel) LoadTaskImage(ctx context.Context, mounts *fs.MountNamespace, root, wd *fs.Dirent, maxTraversals uint, filename string, argv, envv []string, fs *cpuid.FeatureSet) (*TaskContext, error) {
+func (k *Kernel) LoadTaskImage(ctx context.Context, mounts *fs.MountNamespace, root, wd *fs.Dirent, maxTraversals *uint, filename string, argv, envv []string, fs *cpuid.FeatureSet) (*TaskContext, error) {
// Prepare a new user address space to load into.
m := mm.NewMemoryManager(k)
defer m.DecUsers(ctx)
diff --git a/pkg/sentry/loader/elf.go b/pkg/sentry/loader/elf.go
index 9b1e81dc9..385ad0102 100644
--- a/pkg/sentry/loader/elf.go
+++ b/pkg/sentry/loader/elf.go
@@ -610,7 +610,7 @@ func loadInterpreterELF(ctx context.Context, m *mm.MemoryManager, f *fs.File, in
//
// Preconditions:
// * f is an ELF file
-func loadELF(ctx context.Context, m *mm.MemoryManager, mounts *fs.MountNamespace, root, wd *fs.Dirent, maxTraversals uint, fs *cpuid.FeatureSet, f *fs.File) (loadedELF, arch.Context, error) {
+func loadELF(ctx context.Context, m *mm.MemoryManager, mounts *fs.MountNamespace, root, wd *fs.Dirent, maxTraversals *uint, fs *cpuid.FeatureSet, f *fs.File) (loadedELF, arch.Context, error) {
bin, ac, err := loadInitialELF(ctx, m, fs, f)
if err != nil {
ctx.Infof("Error loading binary: %v", err)
diff --git a/pkg/sentry/loader/loader.go b/pkg/sentry/loader/loader.go
index d1417c4f1..69a090844 100644
--- a/pkg/sentry/loader/loader.go
+++ b/pkg/sentry/loader/loader.go
@@ -55,7 +55,7 @@ func readFull(ctx context.Context, f *fs.File, dst usermem.IOSequence, offset in
// installed in the Task FDMap. The caller takes ownership of both.
//
// name must be a readable, executable, regular file.
-func openPath(ctx context.Context, mm *fs.MountNamespace, root, wd *fs.Dirent, maxTraversals uint, name string) (*fs.Dirent, *fs.File, error) {
+func openPath(ctx context.Context, mm *fs.MountNamespace, root, wd *fs.Dirent, maxTraversals *uint, name string) (*fs.Dirent, *fs.File, error) {
if name == "" {
ctx.Infof("cannot open empty name")
return nil, nil, syserror.ENOENT
@@ -136,9 +136,9 @@ const (
// * arch.Context matching the binary arch
// * fs.Dirent of the binary file
// * Possibly updated argv
-func loadPath(ctx context.Context, m *mm.MemoryManager, mounts *fs.MountNamespace, root, wd *fs.Dirent, maxTraversals uint, fs *cpuid.FeatureSet, filename string, argv, envv []string) (loadedELF, arch.Context, *fs.Dirent, []string, error) {
+func loadPath(ctx context.Context, m *mm.MemoryManager, mounts *fs.MountNamespace, root, wd *fs.Dirent, remainingTraversals *uint, fs *cpuid.FeatureSet, filename string, argv, envv []string) (loadedELF, arch.Context, *fs.Dirent, []string, error) {
for i := 0; i < maxLoaderAttempts; i++ {
- d, f, err := openPath(ctx, mounts, root, wd, maxTraversals, filename)
+ d, f, err := openPath(ctx, mounts, root, wd, remainingTraversals, filename)
if err != nil {
ctx.Infof("Error opening %s: %v", filename, err)
return loadedELF{}, nil, nil, nil, err
@@ -163,7 +163,7 @@ func loadPath(ctx context.Context, m *mm.MemoryManager, mounts *fs.MountNamespac
switch {
case bytes.Equal(hdr[:], []byte(elfMagic)):
- loaded, ac, err := loadELF(ctx, m, mounts, root, wd, maxTraversals, fs, f)
+ loaded, ac, err := loadELF(ctx, m, mounts, root, wd, remainingTraversals, fs, f)
if err != nil {
ctx.Infof("Error loading ELF: %v", err)
return loadedELF{}, nil, nil, nil, err
@@ -196,7 +196,7 @@ func loadPath(ctx context.Context, m *mm.MemoryManager, mounts *fs.MountNamespac
// Preconditions:
// * The Task MemoryManager is empty.
// * Load is called on the Task goroutine.
-func Load(ctx context.Context, m *mm.MemoryManager, mounts *fs.MountNamespace, root, wd *fs.Dirent, maxTraversals uint, fs *cpuid.FeatureSet, filename string, argv, envv []string, extraAuxv []arch.AuxEntry, vdso *VDSO) (abi.OS, arch.Context, string, error) {
+func Load(ctx context.Context, m *mm.MemoryManager, mounts *fs.MountNamespace, root, wd *fs.Dirent, maxTraversals *uint, fs *cpuid.FeatureSet, filename string, argv, envv []string, extraAuxv []arch.AuxEntry, vdso *VDSO) (abi.OS, arch.Context, string, error) {
// Load the binary itself.
loaded, ac, d, argv, err := loadPath(ctx, m, mounts, root, wd, maxTraversals, fs, filename, argv, envv)
if err != nil {
diff --git a/pkg/sentry/socket/unix/unix.go b/pkg/sentry/socket/unix/unix.go
index 334169372..4379486cf 100644
--- a/pkg/sentry/socket/unix/unix.go
+++ b/pkg/sentry/socket/unix/unix.go
@@ -266,7 +266,8 @@ func (s *SocketOperations) Bind(t *kernel.Task, sockaddr []byte) *syserr.Error {
subPath = "/"
}
var err error
- d, err = t.MountNamespace().FindInode(t, root, cwd, subPath, fs.DefaultTraversalLimit)
+ remainingTraversals := uint(fs.DefaultTraversalLimit)
+ d, err = t.MountNamespace().FindInode(t, root, cwd, subPath, &remainingTraversals)
if err != nil {
// No path available.
return syserr.ErrNoSuchFile
@@ -314,7 +315,8 @@ func extractEndpoint(t *kernel.Task, sockaddr []byte) (transport.BoundEndpoint,
// Find the node in the filesystem.
root := t.FSContext().RootDirectory()
cwd := t.FSContext().WorkingDirectory()
- d, e := t.MountNamespace().FindInode(t, root, cwd, path, fs.DefaultTraversalLimit)
+ remainingTraversals := uint(fs.DefaultTraversalLimit)
+ d, e := t.MountNamespace().FindInode(t, root, cwd, path, &remainingTraversals)
cwd.DecRef()
root.DecRef()
if e != nil {
diff --git a/pkg/sentry/syscalls/linux/sys_file.go b/pkg/sentry/syscalls/linux/sys_file.go
index 89d21dd98..37c90f6fd 100644
--- a/pkg/sentry/syscalls/linux/sys_file.go
+++ b/pkg/sentry/syscalls/linux/sys_file.go
@@ -92,10 +92,11 @@ func fileOpOn(t *kernel.Task, dirFD kdefs.FD, path string, resolve bool, fn func
root := t.FSContext().RootDirectory()
// Lookup the node.
+ remainingTraversals := uint(linux.MaxSymlinkTraversals)
if resolve {
- d, err = t.MountNamespace().FindInode(t, root, rel, path, linux.MaxSymlinkTraversals)
+ d, err = t.MountNamespace().FindInode(t, root, rel, path, &remainingTraversals)
} else {
- d, err = t.MountNamespace().FindLink(t, root, rel, path, linux.MaxSymlinkTraversals)
+ d, err = t.MountNamespace().FindLink(t, root, rel, path, &remainingTraversals)
}
root.DecRef()
if wd != nil {
@@ -312,7 +313,8 @@ func createAt(t *kernel.Task, dirFD kdefs.FD, addr usermem.Addr, flags uint, mod
fileFlags.LargeFile = true
// Does this file exist already?
- targetDirent, err := t.MountNamespace().FindInode(t, root, d, name, linux.MaxSymlinkTraversals)
+ remainingTraversals := uint(linux.MaxSymlinkTraversals)
+ targetDirent, err := t.MountNamespace().FindInode(t, root, d, name, &remainingTraversals)
var newFile *fs.File
switch err {
case nil:
@@ -997,7 +999,8 @@ func mkdirAt(t *kernel.Task, dirFD kdefs.FD, addr usermem.Addr, mode linux.FileM
}
// Does this directory exist already?
- f, err := t.MountNamespace().FindInode(t, root, d, name, linux.MaxSymlinkTraversals)
+ remainingTraversals := uint(linux.MaxSymlinkTraversals)
+ f, err := t.MountNamespace().FindInode(t, root, d, name, &remainingTraversals)
switch err {
case nil:
// The directory existed.
diff --git a/pkg/sentry/syscalls/linux/sys_thread.go b/pkg/sentry/syscalls/linux/sys_thread.go
index 9eed613a1..c12693ee2 100644
--- a/pkg/sentry/syscalls/linux/sys_thread.go
+++ b/pkg/sentry/syscalls/linux/sys_thread.go
@@ -103,7 +103,8 @@ func Execve(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal
defer wd.DecRef()
// Load the new TaskContext.
- tc, err := t.Kernel().LoadTaskImage(t, t.MountNamespace(), root, wd, linux.MaxSymlinkTraversals, filename, argv, envv, t.Arch().FeatureSet())
+ maxTraversals := uint(linux.MaxSymlinkTraversals)
+ tc, err := t.Kernel().LoadTaskImage(t, t.MountNamespace(), root, wd, &maxTraversals, filename, argv, envv, t.Arch().FeatureSet())
if err != nil {
return 0, nil, err
}