SMB and symlinks. They aren't really symlinks on disk?

seanm

Guru
Joined
Jun 11, 2018
Messages
570
If I create the following files on my Mac:

touch A.txt
ln -s A.txt B.txt


then copy them both to a SMB share on FreeNAS, things work. But I have the impression some kind of illusion is going on. If I ssh to my FreeNAS and list the folder, I see:

-rwxrwx---+ 1 sean MyGroup uarch 1894 Aug 22 17:24 A.txt*
-rwxrwx---+ 1 sean MyGroup uarch 1067 Aug 26 16:44 B.txt*


Note that B.txt does not have an 'l', i.e. it's not actually a symlink! It seems Samba presents it as one, but in fact it isn't. Is this normal? I'd like to understand what's going on here... :) Thanks.
 

anodos

Sambassador
iXsystems
Joined
Mar 6, 2014
Messages
9,545
Code:
        /*
         * Windows systems requires special user access to create a reparse symlinks.
         * They default to only allow administrator symlink create access. This can
         * be changed on the server, but we are going to run into this issue. So if
         * we get an access error on the fsctl then we assume this user doesn't have
         * create symlink rights and we need to fallback to the old Conrad/Steve
         * symlinks. Since the create worked, we know the user has access to the file
         * system, they just don't have create symlink rights. We never fallback if
         * the server is running darwin.
         */
        if ((ioctl_error) && !(UNIX_SERVER(SSTOVC(share)))) {
            /*
             * <14281932> Could be NetApp server not supporting reparse
             * points and returning STATUS_INVALID_DEVICE_REQUEST.
             */
            if ((ioctl_error == EACCES) ||
                (ioctlp->ret_ntstatus == STATUS_INVALID_DEVICE_REQUEST)) {
                smp->sm_flags &= ~MNT_SUPPORTS_REPARSE_SYMLINKS;
              
                error = smbfs_smb_create_windows_symlink(share, create_np,
                                                         namep, name_len,
                                                         targetp, target_len,
                                                         fap, context);
                if (!error) {                          
                    SMBDEBUG("smbfs_smb_create_windows_symlink failed %d\n", error);
                } 
            } 
        } 


smb-759.40.1/kernel/smbfs/smbfs_smb_2.c

Code:
/*
* This server doesn't support UNIX or reparse point style symbolic links, so
* create a faked up symbolic link, using the Conrad and Steve French method
* for storing and reading symlinks on Window Servers.
*
* NOTE: We should remove creating these in the future, but first we need to see
* if we can get reparse point style symbolic links working.
*
* The calling routine must hold a reference on the share
*/
int
smbfs_smb_create_windows_symlink(struct smb_share *share, struct smbnode *dnp,
                                 const char *name, size_t nmlen,
                                 char *target, size_t targetlen,
                                 struct smbfattr *fap, vfs_context_t context)
{
    uint32_t wlen = 0;
    uio_t uio = NULL;
    char *wdata = NULL;
    int error;
    SMBFID fid = 0;
    uint64_t create_flags = SMB2_CREATE_DO_CREATE | SMB2_CREATE_GET_MAX_ACCESS;

    wdata = smbfs_create_windows_symlink_data(target, targetlen, &wlen);
    if (!wdata) {
        error = ENOMEM;
        goto done;
    }

    uio = uio_create(1, 0, UIO_SYSSPACE, UIO_WRITE);
    if (uio == NULL) {
        error = ENOMEM;
        goto done;
    }

    uio_addiov(uio, CAST_USER_ADDR_T(wdata), wlen);

    if (SSTOVC(share)->vc_flags & SMBV_SMB2) {
        /* SMB 2/3  - compound Create/Write/Close */
        error = smb2fs_smb_cmpd_create_write(share, dnp,
                                             name, nmlen,
                                             NULL, 0,
                                             SMB2_FILE_WRITE_DATA, fap,
                                             create_flags, uio,
                                             NULL, 0,
                                             context);
    }
    else {
        /* SMB 1 */
        error = smbfs_smb_create(share, dnp, name, nmlen, SMB2_FILE_WRITE_DATA,
                                 &fid, FILE_CREATE, 0, fap, context);
        if (error) {
            goto done;
        }

        error = smb_smb_write(share, fid, uio, 0, context);

        (void) smbfs_smb_close(share, fid, context);
    }
 
    if (!error) {
        /* We just changed the size of the file */
        fap->fa_size = wlen;
    }

done:
    if (uio != NULL) {
        uio_free(uio);
    }

    if (wdata) {
        SMB_FREE(wdata, M_TEMP);
    }
 
    if (error) {
        SMBWARNING("Creating symlink for %s failed! error = %d\n", name, error);
    }
 
    return error;
}


Code:
/*
 * Create the data required for a faked up symbolic link. This is Conrad and Steve
 * French method for storing and reading symlinks on Window Servers.
 */
static void *
smbfs_create_windows_symlink_data(const char *target, size_t targetlen,
                                                                  uint32_t *rtlen)
{
        MD5_CTX md5;
        uint32_t state[4];
        uint32_t datalen, filelen;
        char *wbuf, *wp;
        int      maxwplen;
        uint32_t targlen = (uint32_t)targetlen;

        datalen = SMB_SYMHDRLEN + targlen;
        filelen = SMB_SYMLEN;
        maxwplen = filelen;

        SMB_MALLOC(wbuf, void *, filelen, M_TEMP, M_WAITOK);

        wp = wbuf;
        bcopy(smb_symmagic, wp, SMB_SYMMAGICLEN);
        wp += SMB_SYMMAGICLEN;
        maxwplen -= SMB_SYMMAGICLEN;
        (void)snprintf(wp, maxwplen, "%04d\n", targlen);
        wp += SMB_SYMLENLEN;
        maxwplen -= SMB_SYMLENLEN;
        MD5Init(&md5);
        MD5Update(&md5, (unsigned char *)target, targlen);
        MD5Final((u_char *)state, &md5);
        (void)snprintf(wp, maxwplen, "%08x%08x%08x%08x\n", htobel(state[0]),
                                   htobel(state[1]), htobel(state[2]), htobel(state[3]));
        wp += SMB_SYMMD5LEN;
        bcopy(target, wp, targlen);
        wp += targlen;
        if (datalen < filelen) {
                *wp++ = '\n';
                datalen++;
                if (datalen < filelen)
                        memset((caddr_t)wp, ' ', filelen - datalen);
        }
        *rtlen = filelen;
        return wbuf;
}

smb-759.40.1/kernel/smbfs/smbfs_smb.c
 
Last edited:

seanm

Guru
Joined
Jun 11, 2018
Messages
570
Enabling 'Unix extensions' I understand (and have enabled). But SMB1 I don't want to turn on since its security is defeated. Odd that server-side symlinks would require an old protocol, any idea why it is so? And is it a Samba thing? FreeBSD thing? Or FreeNAS thing? Thanks!
 

anodos

Sambassador
iXsystems
Joined
Mar 6, 2014
Messages
9,545
Enabling 'Unix extensions' I understand (and have enabled). But SMB1 I don't want to turn on since its security is defeated. Odd that server-side symlinks would require an old protocol, any idea why it is so? And is it a Samba thing? FreeBSD thing? Or FreeNAS thing? Thanks!
The implementation of server-side symlinks in SMB1 has been a security nightmare for a long time. They are implemented differently in SMB3 posix extensions thankfully.
 

seanm

Guru
Joined
Jun 11, 2018
Messages
570
Sorry, I'm confused... :)

To recap, I have:
- "Unix extensions" enabled
- "server min protocol = SMB3_02"

When I copy a symlink from macOS to FreeNAS I get these Minshall+French symlink files. Should I be? Or should I be having real UNIX symlinks (as seen by 'ls' on my ZFS disk)? Am I missing some additional setting?

In case it helps, here my 'testparm -s':

Code:
[global]
    aio max threads = 2
    deadtime = 15
    disable netbios = Yes
    disable spoolss = Yes
    dns proxy = No
    dos charset = CP437
    hostname lookups = Yes
    kernel change notify = No
    lm announce = Yes
    load printers = No
    logging = file
    max log size = 51200
    max open files = 1882991
    nsupdate command = /usr/local/bin/samba-nsupdate -g
    obey pam restrictions = Yes
    printcap name = /dev/null
    security = USER
    server min protocol = SMB3_02
    server role = standalone server
    server string = FreeNAS Server
    smb ports = 445
    time server = Yes
    idmap config *: range = 90000001-100000000
    fruit:nfs_aces = no
    idmap config * : backend = tdb
    acl allow execute always = Yes
    create mask = 0666
    directory mask = 0777
    directory name cache size = 0
    dos filemode = Yes
    strict locking = No

[Production]
    aio write size = 0
    mangled names = illegal
    path = "/mnt/ekur/shares/Production"
    read only = No
    smb encrypt = required
    veto files = /.windows/.mac/
    vfs objects = shadow_copy2 audit catia zfs_space zfsacl fruit streams_xattr
    zfsacl:expose_snapdir = True
    zfsacl:acesort = dontcare
    nfs4:chown = true
    nfs4:acedup = merge
    nfs4:mode = simple
    shadow:snapdirseverywhere = yes
    shadow:format = auto-%Y%m%d.%H%M-1m
    shadow:localtime = yes
    shadow:sort = desc
    shadow:snapdir = .zfs/snapshot
    fruit:resource = stream
    fruit:metadata = stream
 

anodos

Sambassador
iXsystems
Joined
Mar 6, 2014
Messages
9,545
Sorry, I'm confused... :)

To recap, I have:
- "Unix extensions" enabled
- "server min protocol = SMB3_02"

When I copy a symlink from macOS to FreeNAS I get these Minshall+French symlink files. Should I be? Or should I be having real UNIX symlinks (as seen by 'ls' on my ZFS disk)? Am I missing some additional setting?

In case it helps, here my 'testparm -s':

Code:
[global]
    aio max threads = 2
    deadtime = 15
    disable netbios = Yes
    disable spoolss = Yes
    dns proxy = No
    dos charset = CP437
    hostname lookups = Yes
    kernel change notify = No
    lm announce = Yes
    load printers = No
    logging = file
    max log size = 51200
    max open files = 1882991
    nsupdate command = /usr/local/bin/samba-nsupdate -g
    obey pam restrictions = Yes
    printcap name = /dev/null
    security = USER
    server min protocol = SMB3_02
    server role = standalone server
    server string = FreeNAS Server
    smb ports = 445
    time server = Yes
    idmap config *: range = 90000001-100000000
    fruit:nfs_aces = no
    idmap config * : backend = tdb
    acl allow execute always = Yes
    create mask = 0666
    directory mask = 0777
    directory name cache size = 0
    dos filemode = Yes
    strict locking = No

[Production]
    aio write size = 0
    mangled names = illegal
    path = "/mnt/ekur/shares/Production"
    read only = No
    smb encrypt = required
    veto files = /.windows/.mac/
    vfs objects = shadow_copy2 audit catia zfs_space zfsacl fruit streams_xattr
    zfsacl:expose_snapdir = True
    zfsacl:acesort = dontcare
    nfs4:chown = true
    nfs4:acedup = merge
    nfs4:mode = simple
    shadow:snapdirseverywhere = yes
    shadow:format = auto-%Y%m%d.%H%M-1m
    shadow:localtime = yes
    shadow:sort = desc
    shadow:snapdir = .zfs/snapshot
    fruit:resource = stream
    fruit:metadata = stream
You need SMB1 to use these, but I wouldn't get too attached to them. SMB1 is being disabled by default upstream in Samba 4.11, and support will possibly be entirely removed in Samba 5.0.
 

seanm

Guru
Joined
Jun 11, 2018
Messages
570
1) "You need SMB1 to get real UNIX symlinks" or
2) "You need SMB1 to get Minshall+French symlinks

OK, it must be (1).

On a test server, I set:

server min protocol = NT1
server max protocol = NT1

connected from my Mac, copied a folder with some symlinks, ssh'ed to server, checked with smbstatus that I'm indeed connected with NT1 (aka SMB1), and listed the folder on disk. I see real symlinks.

If I set:

server min protocol = SMB2_10
server max protocol = SMB2_10

I end up with Minshall+French symlinks.

If I set:

server min protocol = SMB3_02
server max protocol = SMB3_02

I end up with Minshall+French symlinks.

@anodos is this all expected? The code you posted above says things like "fallback to the old Conrad/Steve symlinks", implying they are legacy, so I would have thought using the newest protocols on the newest FreeNAS would not use them.

Thanks.
 

anodos

Sambassador
iXsystems
Joined
Mar 6, 2014
Messages
9,545
@anodos is this all expected?
Yes.

The code you posted above says things like "fallback to the old Conrad/Steve symlinks", implying they are legacy, so I would have thought using the newest protocols on the newest FreeNAS would not use them.
There's always a danger of reading too much into comments in code. You can compare behavior and pcaps of Mac->Windows, Mac->Mac for symlink creation. You can also review this usenix presentation: https://www.usenix.org/conference/vault19/presentation/allison
 

seanm

Guru
Joined
Jun 11, 2018
Messages
570
Thanks! So what I gather from that video is that there will be something newer than Minshall+French symlinks coming eventually, that uses Extended Attributes.

So my take away here is: SMB is an abstraction that one should not try to look behind. In our case, we were looking at a folder both via ssh+rsync and via SMB. But really one should just *always* go through SMB!
 
Top