Commit 1dec4136 authored by radai-rosenblatt's avatar radai-rosenblatt
Browse files

nfs3 READDIR - avoid NPE for empty dirList from vfs, return NFSERR_TOOSMALL for tiny count values

parent 49432cd2
......@@ -67,6 +67,12 @@
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<!-- to get console logging during development/debug -->
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
......
......@@ -880,6 +880,14 @@ public class NfsServerV3 extends nfs3_protServerStub {
*/
throw new BadCookieException("readdir verifier expired");
}
if (startValue >= dirList.size()) {
res.status = nfsstat.NFSERR_BAD_COOKIE;
res.resfail = new READDIR3resfail();
res.resfail.dir_attributes = new post_op_attr();
res.resfail.dir_attributes.attributes_follow = false;
return res;
}
} else {
cookieverf = generateDirectoryVerifier(dir, fs);
InodeCacheEntry<cookieverf3> cacheKey = new InodeCacheEntry<>(dir, cookieverf);
......@@ -897,14 +905,6 @@ public class NfsServerV3 extends nfs3_protServerStub {
}
}
if (startValue > dirList.size()) {
res.status = nfsstat.NFSERR_BAD_COOKIE;
res.resfail = new READDIR3resfail();
res.resfail.dir_attributes = new post_op_attr();
res.resfail.dir_attributes.attributes_follow = false;
return res;
}
res.status = nfsstat.NFS_OK;
res.resok = new READDIR3resok();
res.resok.reply = new dirlist3();
......@@ -915,6 +915,11 @@ public class NfsServerV3 extends nfs3_protServerStub {
res.resok.cookieverf = cookieverf;
if (dirList.isEmpty()) {
res.resok.reply.eof = true;
return res;
}
int currcount = READDIR3RESOK_SIZE;
res.resok.reply.entries = new entry3();
entry3 currentEntry = res.resok.reply.entries;
......@@ -933,7 +938,16 @@ public class NfsServerV3 extends nfs3_protServerStub {
// check if writing this entry exceeds the count limit
int newSize = ENTRY3_SIZE + name.length();
if (currcount + newSize > arg1.count.value.value) {
lastEntry.nextentry = null;
if (lastEntry != null) {
lastEntry.nextentry = null;
} else {
//corner case - means we didnt have enough space to
//write even a single entry.
res.status = nfsstat.NFSERR_TOOSMALL;
res.resfail = new READDIR3resfail();
res.resfail.dir_attributes = defaultPostOpAttr();
return res;
}
res.resok.reply.eof = false;
......
package org.dcache.nfs.v3;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.dcache.nfs.ExportFile;
import org.dcache.nfs.nfsstat;
import org.dcache.nfs.v3.xdr.READDIR3args;
import org.dcache.nfs.v3.xdr.READDIR3res;
import org.dcache.nfs.vfs.DirectoryEntry;
import org.dcache.nfs.vfs.FileHandle;
import org.dcache.nfs.vfs.Inode;
import org.dcache.nfs.vfs.Stat;
import org.dcache.nfs.vfs.VirtualFileSystem;
import org.dcache.testutils.NfsV3Ops;
import org.dcache.testutils.RpcCallBuilder;
import org.dcache.xdr.RpcCall;
import org.dcache.xdr.XdrAble;
import org.dcache.xdr.XdrEncodingStream;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
public class NfsServerV3READDIR_3Test {
private FileHandle dirHandle;
private Inode dirInode;
private Stat dirStat;
private VirtualFileSystem vfs;
private NfsServerV3 nfsServer;
@Before
public void setup() throws Exception {
dirHandle = new FileHandle(0, 1, 0, new byte[] { 0, 0, 0, 1 }); // the dir we want to read
dirInode = new Inode(dirHandle);
dirStat = new Stat(); // the stat marking it as a dir
//noinspection OctalInteger
dirStat.setMode(Stat.S_IFDIR | 0755);
dirStat.setMTime(System.currentTimeMillis());
vfs = Mockito.mock(VirtualFileSystem.class); // the vfs serving it
Mockito.when(vfs.getattr(Mockito.eq(dirInode))).thenReturn(dirStat);
ExportFile exportFile = new ExportFile(this.getClass().getResource("simpleExports")); // same package as us
nfsServer = new NfsServerV3(exportFile, vfs);
}
@Test
public void testReadDirWithNoResults() throws Exception {
// vfs will return an empty list from the vfs for dir (technically legal)
Mockito.when(vfs.list(Mockito.eq(new Inode(dirHandle)))).thenReturn(Collections.<DirectoryEntry> emptyList());
// set up and execute the call
RpcCall call = new RpcCallBuilder().from("1.2.3.4", "someHost.acme.com", 42).nfs3().noAuth().build();
READDIR3args args = NfsV3Ops.readDir(dirHandle);
READDIR3res result = nfsServer.NFSPROC3_READDIR_3(call, args);
Assert.assertEquals(nfsstat.NFS_OK, result.status);
Assert.assertNull(result.resok.reply.entries); //no entries
Assert.assertTrue(result.resok.reply.eof); //eof
assertXdrEncodable(result);
}
@Test
public void testReadDirWithTinyLimit() throws Exception {
// vfs will return only "." and ".." as contents, both leading to itself
List<DirectoryEntry> dirContents = new ArrayList<>();
dirContents.add(new DirectoryEntry(".", dirInode, dirStat));
dirContents.add(new DirectoryEntry("..", dirInode, dirStat));
Mockito.when(vfs.list(Mockito.eq(dirInode))).thenReturn(dirContents);
// set up and execute the 1st call - no cookie, but very tight size limit
RpcCall call = new RpcCallBuilder().from("1.2.3.4", "someHost.acme.com", 42).nfs3().noAuth().build();
READDIR3args args = NfsV3Ops.readDir(dirHandle, 10); //10 bytes - not enough for anything
READDIR3res result = nfsServer.NFSPROC3_READDIR_3(call, args);
Assert.assertEquals(nfsstat.NFSERR_TOOSMALL, result.status); //error response
}
@Test
public void testContinueReadingAfterEOF() throws Exception {
// vfs will return only "." and ".." as contents, both leading to itself
List<DirectoryEntry> dirContents = new ArrayList<>();
dirContents.add(new DirectoryEntry(".", dirInode, dirStat));
dirContents.add(new DirectoryEntry("..", dirInode, dirStat));
Mockito.when(vfs.list(Mockito.eq(dirInode))).thenReturn(dirContents);
// set up and execute the 1st call - no cookie, but very tight size limit
RpcCall call = new RpcCallBuilder().from("1.2.3.4", "someHost.acme.com", 42).nfs3().noAuth().build();
READDIR3args args = NfsV3Ops.readDir(dirHandle);
READDIR3res result = nfsServer.NFSPROC3_READDIR_3(call, args);
Assert.assertEquals(nfsstat.NFS_OK, result.status); //response ok
Assert.assertTrue(result.resok.reply.eof); //eof
assertXdrEncodable(result);
// client violates spec - attempts to read more
// using cookie on last (2nd) entry and returned verifier
long cookie = result.resok.reply.entries.nextentry.cookie.value.value;
byte[] cookieVerifier = result.resok.cookieverf.value;
args = NfsV3Ops.readDir(dirHandle, cookie, cookieVerifier);
result = nfsServer.NFSPROC3_READDIR_3(call, args);
Assert.assertEquals(nfsstat.NFSERR_BAD_COOKIE, result.status); //error response
assertXdrEncodable(result);
}
private void assertXdrEncodable (XdrAble xdrAble) {
try {
XdrEncodingStream outputStream = Mockito.mock(XdrEncodingStream.class);
xdrAble.xdrEncode(outputStream); // should not blow up
} catch (Exception e) {
throw new AssertionError("object does not survive xdr encoding", e);
}
}
}
package org.dcache.testutils;
import java.net.InetAddress;
import org.mockito.Mockito;
public class InetAddressBuilder {
private String ipAddress;
private String hostName;
public InetAddressBuilder ip(String ipAddress) {
this.ipAddress = ipAddress;
return this;
}
public InetAddressBuilder hostName(String hostName) {
this.hostName = hostName;
return this;
}
public InetAddress build() {
InetAddress address = Mockito.mock(InetAddress.class);
Mockito.when(address.getHostAddress()).thenReturn(ipAddress);
Mockito.when(address.getHostName()).thenReturn(hostName);
return address;
}
}
package org.dcache.testutils;
import org.dcache.nfs.v3.xdr.READDIR3args;
import org.dcache.nfs.v3.xdr.cookie3;
import org.dcache.nfs.v3.xdr.cookieverf3;
import org.dcache.nfs.v3.xdr.count3;
import org.dcache.nfs.v3.xdr.nfs3_prot;
import org.dcache.nfs.v3.xdr.nfs_fh3;
import org.dcache.nfs.v3.xdr.uint32;
import org.dcache.nfs.v3.xdr.uint64;
import org.dcache.nfs.vfs.FileHandle;
public class NfsV3Ops {
public static READDIR3args readDir(FileHandle requestedDirHandle) {
return readDir(requestedDirHandle, 0, new byte[nfs3_prot.NFS3_COOKIEVERFSIZE], Integer.MAX_VALUE);
}
public static READDIR3args readDir(FileHandle requestedDirHandle, int maxResponseSize) {
return readDir(requestedDirHandle, 0, new byte[nfs3_prot.NFS3_COOKIEVERFSIZE], maxResponseSize);
}
public static READDIR3args readDir(FileHandle requestedDirHandle, long cookie, byte[] cookieVerifier) {
return readDir(requestedDirHandle, cookie, cookieVerifier, Integer.MAX_VALUE);
}
public static READDIR3args readDir(FileHandle requestedDirHandle, long cookie, byte[] cookieVerifier, int maxResponseBytes) {
READDIR3args args = new READDIR3args();
args.dir = new nfs_fh3();
args.dir.data = requestedDirHandle.bytes();
args.cookie = new cookie3(new uint64(cookie));
args.cookieverf = new cookieverf3(cookieVerifier);
args.count = new count3(new uint32(maxResponseBytes));
return args;
}
}
package org.dcache.testutils;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import org.dcache.nfs.v3.xdr.nfs3_prot;
import org.dcache.xdr.RpcAuth;
import org.dcache.xdr.RpcAuthTypeNone;
import org.dcache.xdr.RpcCall;
import org.dcache.xdr.XdrTransport;
import org.mockito.Mockito;
public class RpcCallBuilder {
private InetAddressBuilder sourceAddressBuilder = new InetAddressBuilder();
private int sourcePort;
private int program;
private int version;
private RpcAuth rpcAuth;
public RpcCallBuilder nfs3() {
this.program = nfs3_prot.NFS_PROGRAM;
this.version = nfs3_prot.NFS_V3;
return this;
}
public RpcCallBuilder from(String sourceIpAddress, String sourceHostName, int sourcePort) {
sourceAddressBuilder.ip(sourceIpAddress).hostName(sourceHostName);
this.sourcePort = sourcePort;
return this;
}
public RpcCallBuilder noAuth() {
rpcAuth = new RpcAuthTypeNone();
return this;
}
public RpcCall build() {
InetAddress clientAddress = sourceAddressBuilder.build();
InetSocketAddress socketAddress = new InetSocketAddress(clientAddress, sourcePort);
XdrTransport transport = Mockito.mock(XdrTransport.class);
Mockito.when(transport.getRemoteSocketAddress()).thenReturn(socketAddress);
return new RpcCall(program, version, rpcAuth, transport);
}
}
/ *(rw,all_root,sec=none)
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment