All of lore.kernel.org
 help / color / mirror / Atom feed
* 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.