]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
smb/client: fix incorrect nlink returned by fstat()
authorChenXiaoSong <chenxiaosong@kylinos.cn>
Thu, 9 Jul 2026 02:57:03 +0000 (10:57 +0800)
committerSteve French <stfrench@microsoft.com>
Thu, 9 Jul 2026 12:55:55 +0000 (07:55 -0500)
Reproducer:

  1. mount -t cifs //${server_ip}/export /mnt
  2. touch /mnt/file1; ln /mnt/file1 /mnt/file2; ln /mnt/file1 /mnt/file3
  3. C program: int fd = open("/mnt/file1", O_RDONLY);
  4. C program: struct stat stbuf; fstat(fd, &stbuf);
                stbuf.st_nlink is always 1, should be 3

Setting `unknown_nlink` to true in `SMB2_open()` triggers the
`CIFS_FATTR_UNKNOWN_NLINK` flag in `cifs_open_info_to_fattr()`,
which safely preserves the existing i_nlink in
`cifs_nlink_fattr_to_inode()`.

See the detailed procedure below:

  path_openat
    open_last_lookups
      lookup_open
        atomic_open
          cifs_atomic_open // dir->i_op->atomic_open
            cifs_lookup
              cifs_get_inode_info
                cifs_get_fattr
                  smb2_query_path_info // server->ops->query_path_info
                    smb2_compound_op
                      SMB2_open_init
                      case SMB2_OP_QUERY_INFO
                      SMB2_query_info_init(FILE_ALL_INFORMATION,)
                  cifs_open_info_to_fattr
                    fattr->cf_nlink = le32_to_cpu(info->NumberOfLinks)
                update_inode_info
                  cifs_iget
                    cifs_fattr_to_inode
                      cifs_nlink_fattr_to_inode
                        set_nlink(inode, fattr->cf_nlink)
    do_open
      vfs_open
        do_dentry_open
          cifs_open
            cifs_nt_open
              smb2_open_file // server->ops->open
                SMB2_open
                  buf->unknown_nlink = true
              cifs_get_inode_info
                cifs_get_fattr
                  cifs_open_info_to_fattr
                    if (data->unknown_nlink) // true
                    fattr->cf_flags |= CIFS_FATTR_UNKNOWN_NLINK
                update_inode_info
                  cifs_fattr_to_inode
                    cifs_nlink_fattr_to_inode
                      if (fattr->cf_flags & CIFS_FATTR_UNKNOWN_NLINK) // true
                      return // do not modify nlink

Signed-off-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/smb/client/cifsglob.h
fs/smb/client/inode.c
fs/smb/client/smb2pdu.c

index 99f9e6dca62b60b056a31b67c6874ffaae35f076..08e94633a9c1e28ceacfde197a57d0cc4a938023 100644 (file)
@@ -250,6 +250,7 @@ struct cifs_open_info_data {
        bool adjust_tz;
        bool reparse_point;
        bool contains_posix_file_info;
+       bool unknown_nlink;
        struct {
                /* ioctl response buffer */
                struct {
index 2ed1c79c1132564ed716b95cb2bfb2f961681a49..16a3ddff060f2edf70ac827eab44c4fd8dee313b 100644 (file)
@@ -909,6 +909,8 @@ static void cifs_open_info_to_fattr(struct cifs_fattr *fattr,
        struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
 
        memset(fattr, 0, sizeof(*fattr));
+       if (data->unknown_nlink)
+               fattr->cf_flags |= CIFS_FATTR_UNKNOWN_NLINK;
        fattr->cf_cifsattrs = le32_to_cpu(info->Attributes);
        if (info->DeletePending)
                fattr->cf_flags |= CIFS_FATTR_DELETE_PENDING;
index bc77170458b362b28c1d2fc7ca74bacc74da130f..8f83ab377db143354c7d052739b7a15cd9dfbf32 100644 (file)
@@ -3380,6 +3380,7 @@ replay_again:
                file_info->EndOfFile = rsp->EndofFile;
                file_info->Attributes = rsp->FileAttributes;
                file_info->NumberOfLinks = cpu_to_le32(1);
+               buf->unknown_nlink = true;
                file_info->DeletePending = 0; /* successful open = not delete pending */
        }