* NFS client crash if server is confused about file handles
@ 2021-11-09 19:48 rtm
0 siblings, 0 replies; only message in thread
From: rtm @ 2021-11-09 19:48 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker; +Cc: linux-nfs
[-- Attachment #1: Type: text/plain, Size: 1527 bytes --]
_nfs4_open_and_get_state() calls d_splice_alias() and assumes that a
non-NULL returned dentry pointer is valid. However, d_splice_alias()
returns ERR_PTR(-ELOOP) if the server assigns the same file handle to
a file and its directory. This causes a later crash when
nfs_set_verifier() tries to use the dentry pointer.
I've attached a demo program, which works for me on kernel 5.15 on
riscv, and 5.4.0 on amd64.
# cc nfs_5.c
# ./a.out
...
[ 29.702595] VFS: Lookup of 'x' in nfs4 0:16 would have caused loop
[ 29.710258] Unable to handle kernel NULL pointer dereference at virtual address 0000000000000034
[ 29.754320] epc : ffffffff8004e24e ra : ffffffff807a8dc2 sp : ffffffd00056b960
[ 29.843403] status: 0000000200000121 badaddr: 0000000000000034 cause: 000000000000000d
[ 29.851434] [<ffffffff8004e24e>] do_raw_spin_lock+0xa/0xd8
[ 29.857532] [<ffffffff807a8dc2>] _raw_spin_lock+0x1a/0x22
[ 29.863650] [<ffffffff80205bf2>] nfs_set_verifier+0x20/0x6e
[ 29.869795] [<ffffffff8022a718>] nfs4_do_open.constprop.0+0x3f6/0x774
[ 29.877084] [<ffffffff8022ab2e>] nfs4_atomic_open+0xc/0x1c
[ 29.883255] [<ffffffff80208162>] nfs_atomic_open+0x194/0x332
[ 29.890784] [<ffffffff8013756e>] path_openat+0x5ca/0xaf6
[ 29.896912] [<ffffffff80138468>] do_filp_open+0x70/0xd0
[ 29.903003] [<ffffffff801276de>] do_sys_openat2+0x1fc/0x298
[ 29.909150] [<ffffffff80128870>] do_sys_open+0x3c/0x78
[ 29.915308] [<ffffffff801288ee>] sys_openat+0x18/0x20
[ 29.921424] [<ffffffff80003046>] ret_from_syscall+0x0/0x2
[-- Attachment #2: nfs_5.c --]
[-- Type: application/octet-stream, Size: 17816 bytes --]
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include <assert.h>
int sym_op = -1;
int sym_skip = 0;
int sym_type = 1; // all fattr4 file types
int sym_fh = 0; // all file handles
int opcounts[256];
long long next_cookie = 3;
int current_fh = 0;
// map file/dir names to file handle
char *fhnames[] = {
"",
"tmp",
"x",
"y",
"z",
0
};
int
name2fh(char *name)
{
for(int i = 0; fhnames[i]; i++){
if(strcmp(name, fhnames[i]) == 0)
return i;
}
return 100;
}
#define NAA 64
unsigned long long aa[NAA] = {
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x80002ull,
0x0ull,
0x0ull,
0x0ull,
0x20000002ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
0x0ull,
};
int aai = 0;
char ibuf[4096];
int ilen = 0;
int ii = 0;
char obuf[4096];
int oi = 0;
int symstart = -1;
int symend = -1;
int readn(int fd, void *xbuf, int n) {
char *buf = (char *) xbuf;
int orig = n;
while(n > 0){
int cc = read(fd, buf, n);
if(cc <= 0) { perror("read"); return -1; }
n -= cc;
buf += cc;
}
return orig;
}
unsigned int
parse32()
{
if(ii >= ilen){
printf("parsed beyond the end of the input\n");
return 0;
}
unsigned int x = *(int*)(ibuf+ii);
ii += 4;
return ntohl(x);
}
unsigned long long
parse64()
{
unsigned long long hi = parse32();
unsigned long long lo = parse32();
return (hi << 32) | lo;
}
// sessionid4 -- 16 bytes
void
parse_sid(char *sid)
{
for(int i = 0; i < 16; i++){
if(sid)
sid[i] = ibuf[ii];
ii++;
}
}
unsigned int
parse_opaque(char *buf)
{
if(buf)
buf[0] = 0;
int nominal_n = parse32();
if(nominal_n > 4096){
printf("crazy opaque length %d\n", nominal_n);
return 0;
}
int real_n = nominal_n;
while((real_n%4) != 0) real_n += 1;
for(int i = 0; i < real_n; i++){
if(buf)
buf[i] = ibuf[ii];
ii++;
}
if(buf)
buf[nominal_n] = 0;
return nominal_n;
}
void
put32(unsigned int x)
{
assert((oi % 4) == 0);
*(int*)(obuf+oi) = htonl(x);
oi += 4;
}
void
put64(unsigned long long x)
{
put32(x >> 32);
put32(x);
}
void
put_opaque(int n, char *buf)
{
put32(n);
for(int i = 0; i < n; i++)
obuf[oi++] = (buf ? buf[i] : 0);
while((n%4)!=0){
obuf[oi++] = 0;
n++;
}
}
void
put_sid(char *sid)
{
for(int i = 0; i < 16; i++){
obuf[oi++] = (sid ? sid[i] : 0);
}
}
void
parse_nop()
{
}
void
parse_op_exchange_id()
{
parse32(); // verifier4, first half
parse32(); // verifier4, second half
parse_opaque(0); // eia_clientowner
int cflags = parse32(); // eia_flags
parse32(); // state_protect4_a.spa_how, assume SP4_NONE
int nimpl = parse32(); // length of client_impl_id
for(int impli = 0; impli < nimpl; impli++){
char junk[512];
parse_opaque(junk); // nii_domain
// printf("nii_domain: %s\n", junk);
parse_opaque(junk); // nii_name
// printf("nii_name: %s\n", junk);
parse64(); // 1/2 of nfstime4
parse32(); // 1/2 of nfstime4
}
// finish EXCHANGE_ID4res
put32(0); // eir_status = NFS4_OK
put64(1); // clientid4
put32(1); // sequenceid4
int sflags = 0x103 | 0x10000; // EXCHGID4_FLAG_USE_NON_PNFS
put32(sflags); // eir_flags
put32(0); // state_protect4_r.spr_how = SP4_NONE
put64(1); // server_owner4.so_minor_id
put32(4); // length of so_major_id<>
put32(0x11223344); // so_major_id<>
put32(4); // length of eir_server_scope
put32(0x11223344);
put32(1); // length of eir_server_impl_id<1>
put32(4); // nfs_impl_id4.nii_domain
put32(0x11223344);
put32(4); // nfs_impl_id4.nii_name
put32(0x11223344);
put64(0); // nii_date 1/2
put32(0); // nii_date 2/2
}
void
parse_op_create_session()
{
parse64(); // csa_clientid
int seq = parse32(); // csa_sequence
parse32(); // csa_flags
// csa_fore_chan_attrs, csa_back_chan_attrs
int attrs[2][6];
for(int i = 0; i < 2; i++){
for(int j = 0; j < 6; j++){
attrs[i][j] = parse32();
}
parse_opaque(0); // ca_rdma_ird<1>
}
put32(0); // OK
for(int i = 0; i < 4; i++)
put32(1); // csr_sessionid i/4
put32(seq); // csr_sequence
put32(0x3); // csr_flags
for(int i = 0; i < 2; i++){
for(int j = 0; j < 6; j++)
put32(attrs[i][j]);
put32(0); // ca_rdma_ird
}
}
void
parse_op_sequence()
{
char sid[16];
parse_sid(sid); // sa_sessionid
int seq = parse32(); // sa_sequenceid
int slot = parse32(); // sa_slotid
int hislot = parse32(); // sa_highest_slotid
parse32(); // sa_cachethis
put32(0); // OK
put_sid(sid); // sr_sessionid
put32(seq); // sr_sequenceid
put32(slot); // sr_slotid
put32(hislot); // sr_highest_slotid
put32(hislot); // sr_target_highest_slotid
put32(0); // sr_status_flags
}
void
parse_op_reclaim_complete()
{
parse32(); // rca_one_fs
put32(0); // rcr_status
}
void
parse_op_putrootfh()
{
// no arguments
put32(0); // OK
current_fh = 0;
}
void
parse_op_secinfo_no_name()
{
parse32(); // secinfo_style4
put32(0); // OK
put32(1); // # of secinfo4
#if 1
put32(0); // flavor = AUTH_NULL
#else
put32(6); // flavor = RPCSEC_GSS
put32(4); // size of sec_oid4
put32(0xffffffff);
put32(0); // qop4
put32(1); // rpc_gss_svc_t
#endif
}
void
parse_op_destroy_session()
{
parse_sid(0);
put32(0); // OK
}
void
parse_op_destroy_clientid()
{
parse64(); // clientid
put32(0); // OK
}
void
parse_op_getfh()
{
// no arguments
put32(0); // OK
int xfh = current_fh;
if(sym_fh) xfh ^= aa[aai++];
put_opaque(4, (char*)&xfh); // fh
}
//
// called by getattr and readdir.
// generates a fattr4 (bitmap4 then attrlist4).
//
void
put_fattr4(int bitwords, int words[], int fh)
{
put32(bitwords);
for(int i = 0; i < bitwords; i++)
put32(words[i]);
int leni = oi;
put32(0); // placeholder for total length of attrs
for(int a = 0; a < bitwords*32; a++){
if(words[a/32] & (1 << (a % 32))){
if(a == 0){
put32(2); // # bitmap words of supported attrs
put32(0xffffffff);
put32(0xffffffff);
} else if(a == 1){
int type = 1;
if(fh == 0 || fh == 1)
type = 2;
if(sym_type) type ^= aa[aai++];
put32(type); // NF4DIR=2 or NF4REG=1
printf(" type %d\n", type);
} else if(a == 2){
put32(0); // fh_expire_type
} else if(a == 3){
put64(0); // change
} else if(a == 4){
put64(4096*10); // size
} else if(a == 5){
put32(1); // link support
} else if(a == 6){
put32(1); // symlink support
} else if(a == 8){
put64(1); // fsid major
put64(1); // fsid minor
} else if(a == 10){
put32(1); // lease time
} else if(a == 11){
put32(0); // rdattr_error
} else if(a == 13){
put32(0xf); // aclsupport
} else if(a == 19){
// filehandle
int xfh = fh;
if(sym_fh) xfh ^= aa[aai++];
put_opaque(4, (char*)&xfh); // fh
printf(" fh %d\n", xfh);
} else if(a == 20){
put64(fh); // fileid
printf(" fileid %d\n", fh);
} else if(a == 27){
put64(0xffffffffffff); // max file size
} else if(a == 28){
put32(0xffff); // max link
} else if(a == 29){
put32(256); // max name
} else if(a == 30){
put64(10*4096); // max read
} else if(a == 31){
put64(10*4096); // max write
} else if(a == 33){
put32(0777); // mode
} else if(a == 35){
put32(3); // numlinks
} else if(a == 36){
put_opaque(6, "other"); // owner
} else if(a == 37){
put_opaque(6, "other"); // owner_group
} else if(a == 41){
put32(1); // rawdev major
put32(1); // rawdev minor
} else if(a == 45){
put64(4096*10); // space used
} else if(a == 47){
put64(0); // time access seconds
put32(0); // nseconds
} else if(a == 51){
put64(0); // time delta seconds
put32(0); // nseconds
} else if(a == 52){
put64(0); // time metadata seconds
put32(0); // nseconds
} else if(a == 53){
put64(0); // time modify seconds
put32(0); // nseconds
} else if(a == 55){
put64(0); // mounted_on_fileid ???
} else if(a == 62){
// fs_layout_types
put32(1);
put32(1); // LAYOUT4_NFSV4_1_FILES
} else if(a == 75){
// FATTR4_SUPPATTR_EXCLCREAT
put32(2); // bitmap length
put32(0xffffffff);
put32(0xffffffff);
} else {
printf("unknown requested attr %d\n", a);
put64(0); // XXX
}
}
}
*(int*)(obuf+leni) = htonl(oi - leni - 4);
}
void
parse_op_getattr()
{
int bitwords = parse32();
int words[64];
for(int i = 0; i < bitwords; i++)
words[i] = parse32();
put32(0); // OK
put_fattr4(bitwords, words, current_fh);
}
void
parse_op_putfh()
{
int fh = -1;
int n = parse_opaque((char*)&fh); // fh
if(n != 4){
printf("op_putfh fh size %d, not 4\n", n);
exit(1);
}
current_fh = fh;
put32(0); // OK
}
void
parse_op_access()
{
int mask = parse32(); // mask of rights to query
put32(0); // OK
put32(0x3f); // supported = all rights
put32(0x3f); // access = all rights
}
void
parse_op_lookup()
{
char name[256];
int n = parse_opaque(name);
name[n>=0?n:0] = '\0';
printf("lookup %s\n", name);
put32(0); // OK
current_fh = name2fh(name);
}
void
parse_op_readdir()
{
long long cookie = parse64();
long long verf = parse64(); // cookie verifier
parse32(); // dircount
parse32(); // maxcount
// attr_request
int bitwords = parse32();
int words[32];
for(int i = 0; i < bitwords; i++)
words[i] = parse32();
put32(0); // OK
put64(verf); // cookieverf
char *names[] = { "z", "zzz" };
for(int i = 0; i < 2; i++){
put32(1); // *nextentry
put64(next_cookie++); // cookie
put_opaque(3, names[i]); // name
put_fattr4(bitwords, words, 1 + i);
}
put32(0); // *nextentry
put32(1); // eof
}
void
parse_op_open()
{
parse32(); // seqid
parse32(); // share_access
parse32(); // share_deny
parse64(); // owner client id
parse_opaque(0); // owner owner
// openflag4
int opentype = parse32();
if(opentype == 1){
// OPEN4_CREATE
int mode = parse32(); // createhow4
if(mode == 0){
// UNCHECKED4
// fattr4 createattrs
int bitwords = parse32();
int words[32];
for(int i = 0; i < bitwords; i++)
words[i] = parse32();
parse_opaque(0); // attrlist4
} else {
printf("OPEN4_CREATE unknown mode %d\n", mode);
exit(1);
}
} else if(opentype == 0){
// OPEN4_NOCREATE
} else {
printf("unknown opentype %d\n", opentype);
exit(1);
}
int open_claim_type = parse32();
if(open_claim_type == 0){
// CLAIM_NULL
parse_opaque(0); // file name
} else if(open_claim_type == 2){
// CLAIM_DELEGATE_CUR
// open_claim_delegate_cur4
// stateid4
parse32(); // seqid
parse32();
parse32();
parse32();
parse_opaque(0); // file name
} else if(open_claim_type == 4){
// CLAIM_FH
} else {
printf("oy, open_claim_type %d\n", open_claim_type);
exit(1);
}
put32(0); // OK
// stateid4
put32(1); // seqid
put32(1); // other
put32(1);
put32(1);
// change_info4
put32(1);
put64(0); // before
put64(0); // after
put32(0); // rflags
put32(0); // attrset bitmap length
// open_delegation4
put32(0); // OPEN_DELEGATE_NONE
printf(" current_fh %d\n", current_fh);
}
void
parse_op_setattr()
{
// stateid4
parse32(); // seqid
parse32(); // other
parse32(); // other
parse32(); // other
// fattr4
int bitwords = parse32();
int words[64];
for(int i = 0; i < bitwords; i++)
words[i] = parse32();
parse_opaque(0); // attrlist4
put32(0); // OK
put32(bitwords);
for(int i = 0; i < bitwords; i++)
put32(words[i]);
}
void
parse_op_layoutget()
{
parse32(); // loga_signal_layout_avail
parse32(); // layouttype4
parse32(); // layoutiomode4
parse64(); // offset
parse64(); // length
parse64(); // minlength
parse32(); // stateid4 seqid
parse32(); // stateid4 other
parse32(); // stateid4 other
parse32(); // stateid4 other
parse32(); // count32
put32(0); // OK
put32(0); // return_on_close
put32(0); // stateid4 seqid
put32(0); // stateid4 other
put32(0); // stateid4 other
put32(0); // stateid4 other
put32(1); // # of layout4
put64(0); // offset
put64(1000000); // length
put32(3); // layoutiomode4
put32(1); // layouttype4
put_opaque(8, "xxxxxxxx"); // loc_body
}
void
parse_op_write()
{
parse32(); // stateid4
parse32(); // stateid4
parse32(); // stateid4
parse32(); // stateid4
parse64(); // offset
parse32(); // stable_how4
int n = parse_opaque(0); // data
put32(0); // OK
put32(n); // count
put32(0); // UNSTABLE4
put64(1); // verifier
}
void
parse_op_read()
{
parse32(); // stateid4
parse32(); // stateid4
parse32(); // stateid4
parse32(); // stateid4
parse64(); // offset
parse32(); // count
put32(0); // OK
put32(1); // eof
put_opaque(4, "abcd");
}
void
parse_compound()
{
char tag[512];
int taglen = parse_opaque(tag); // tag
parse32(); // minor version
int nops = parse32();
// start a COMPOUND4res
put32(0); // nfsstat4 = NFS4_OK
put_opaque(taglen, tag);
put32(nops); // length of resarray<>
for(int opindex = 0; opindex < nops && oi < ilen; opindex++){
int op = parse32();
printf("op %d #%d\n", op, opcounts[op&0xff]);
put32(op); // resop in nfs_resop4
if(sym_op == op){
if(sym_skip == 0){
symstart = oi;
}
sym_skip -= 1;
}
if(op == 42){
parse_op_exchange_id();
} else if(op == 43){
parse_op_create_session();
} else if(op == 53){
parse_op_sequence();
} else if(op == 58){
parse_op_reclaim_complete();
} else if(op == 24){
parse_op_putrootfh();
} else if(op == 52){
parse_op_secinfo_no_name();
} else if(op == 44){
parse_op_destroy_session();
} else if(op == 57){
parse_op_destroy_clientid();
} else if(op == 10){
parse_op_getfh();
} else if(op == 9){
parse_op_getattr();
} else if(op == 22){
parse_op_putfh();
} else if(op == 3){
parse_op_access();
} else if(op == 15){
parse_op_lookup();
} else if(op == 26){
parse_op_readdir();
} else if(op == 18){
parse_op_open();
} else if(op == 34){
parse_op_setattr();
} else if(op == 50){
parse_op_layoutget();
} else if(op == 38){
parse_op_write();
} else if(op == 25){
parse_op_read();
} else {
printf("unknown op %d\n", op);
// cannot continue to the next op since
// we don't know how long this one is.
break;
}
if(symstart != -1)
symend = oi;
opcounts[op&0xff] += 1;
}
}
void
parse_rpc()
{
// SUN RPC
int xid = parse32();
parse32(); // mtype=CALL
parse32(); // rpc version
parse32(); // prog#
parse32(); // prog vers
int proc = parse32();
parse32(); // cred type
parse_opaque(0); // cred
parse32(); // verf type
parse_opaque(0); // verf
put32(xid);
put32(1); // REPLY
put32(0); // MSG_ACCEPTED
put32(0); // opaque_auth flavor = AUTH_NULL
put32(0); // opaque_auth length
put32(0); // SUCCESS
if(proc == 0){
parse_nop();
} else if(proc == 1){
parse_compound();
} else {
printf("unknown rpc proc %d\n", proc);
}
}
int
main(){
setlinebuf(stdout);
struct rlimit r;
r.rlim_cur = r.rlim_max = 0;
setrlimit(RLIMIT_CORE, &r);
int s = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(2049);
int yes = 1;
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
if(bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0){
perror("bind"); exit(1);
}
listen(s, 10);
int pid1 = fork();
if(pid1 == 0){
close(s);
if(system("echo -n mount: ; mount -o nolock 127.0.0.1:/tmp /mnt") == 0){
system("echo -n ls: ; ls -l /mnt/. /mnt/z");
system("echo -n echo: ; echo hi > /mnt/x");
system("echo -n dd: ; dd if=/mnt/y of=/dev/null bs=512 count=1");
system("echo -n umount: ; umount /mnt");
}
exit(0);
}
int pid2 = fork();
if(pid2 == 0){
socklen_t sinlen = sizeof(sin);
printf("calling accept\n");
int s1 = accept(s, (struct sockaddr *) &sin, &sinlen);
printf("accept returned %d\n", s1);
if(s1 < 0) { perror("accept"); exit(1); }
close(s);
while(1){
if(readn(s1, &ilen, 4) < 0) break;
ilen = ntohl(ilen);
ilen &= 0x7fffffff;
if(readn(s1, ibuf, ilen) < 0) break;
oi = ii = 0;
put32(0); // place-holder for length
parse_rpc();
if(aai > NAA){
printf("aai %d NAA %d\n", aai, NAA);
exit(1);
}
*(int*)(obuf+0) = htonl((oi - 4) | 0x80000000);
if(symstart != -1){
for(int i = symstart; i < oi && i < symend && aai < NAA; i += 8)
*(unsigned long long *)(obuf + i) ^= aa[aai++];
symstart = -1;
}
if(write(s1, obuf, oi)<=0) perror("write");
}
exit(1);
}
close(s);
sleep(10);
}
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2021-11-09 19:48 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-11-09 19:48 NFS client crash if server is confused about file handles rtm
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.