Troubleshooting NT_STATUS_ACCESS_DENIED from Samba on Manjaro Linux
A few months ago, I switched my main desktop to Manjaro, and I’m glad about it. Manjaro Linux is a polished and well-designed Linux distribution. As I like simplicity and a minimalistic approach, I chose the XFCE Desktop edition. Switching to Linux did not make me abandon the Windows platform completely. I spend lots of my work and hobby time on this OS. But I run it in QEMU-KVM VMs, configured through the Virtual Manager. As I experiment with various system settings, I have a base VM image and clone it when necessary for new projects/research. Thanks to this configuration, I finally stopped breaking my main system One thing I needed to figure out was a way to share files between my Linux host and Windows VMs. I picked Samba as I wanted something which would look native in Windows. And here my troubleshooting story begins
I could summarize it in one sentence: “always check the system journald log,” but if you’re interested in a more extended and convoluted approach, please read on
When Samba returns NT_STATUS_ACCESS_DENIED
My smb.conf file looks as follows:
[global]
browse list = yes
config backend = file
debug pid = yes
debug timestamp = yes
debug uid = yes
dns proxy = no
follow symlinks = no
guest account = nobody
load printers = no
log file = /var/log/samba/%m.log
log level = 2
logging = systemd file
map to guest = Bad User
max log size = 1000
name resolve order = lmhosts bcast host wins
passdb backend = tdbsam
security = user
server role = standalone server
usershare path = /var/lib/samba/usershare
usershare allow guests = yes
usershare max shares = 100
usershare owner only = yes
workgroup = WORKGROUP
[homes]
browseable = no
comment = Home Directories
create mask = 0660
directory mask = 0770
guest ok = no
read only = no
valid users = %S
[winshare]
browseable = yes
comment = Share directory
guest ok = no
path = /mnt/data/winshare
read only = no
force group = +winshare
valid users = me,ssolnica
[symbols]
browseable = yes
comment = Symbols
guest ok = no
path = /mnt/data/symbols
read only = no
valid users = me
I created the Windows user (smbpasswd -a me
) and enabled smb and nmb services (systemctl enable nmb && systemctl enable smb
). I configured Samba in Server Standalone mode as I did not need any of the AD features (by the way, it’s incredible that you may set up the whole AD in Linux!). When I tried my shares in Windows, the \\mypc.local\me
share was working fine, but \\mypc.local\winshare
was returning NT_STATUS_ACCESS_DENIED
. I stopped the Samba service and ran it manually with debug level set to 3 (alternatively, you could specify debug level
in the smb.conf
file):
# systemctl stop smb
# smbd --no-process-group --foreground -d 3 --debug-stdout
Then, I tried the share in smbclient:
$ smbclient -U me //mypc/winshare
Password for [WORKGROUP\me]:
Try "help" to get a list of possible commands.
smb: \> ls
NT_STATUS_ACCESS_DENIED listing \*
The error reported by Samba pointed to the file system. So I restarted the service and attached strace to it. You need to make sure to trace the child processes (-f/-ff) as the primary Samba server launches a child server for each client session:
strace -p 4350 -ff -o smbd.strace
Here is some interesting content from the output file:
...
readlink("/mnt/data/winshare", 0x7ffe77011d00, 1023) = -1 EINVAL (Invalid argument)
setgroups(12, [956, 1000, 998, 991, 3, 90, 98, 1001, 962, 961, 150, 1002]) = 0
setresgid(-1, 1000, -1) = 0
getegid() = 1000
setresuid(1000, 1000, -1) = 0
geteuid() = 1000
chdir("/mnt/data/winshare") = 0
newfstatat(AT_FDCWD, ".", {st_mode=S_IFDIR|S_ISGID|0770, st_size=4096, ...}, 0) = 0
getcwd("/mnt/data/winshare", 4096) = 19
getcwd("/mnt/data/winshare", 1024) = 19
openat(AT_FDCWD, ".", O_RDONLY|O_NOFOLLOW|O_PATH) = 12
newfstatat(12, "", {st_mode=S_IFDIR|S_ISGID|0770, st_size=4096, ...}, AT_EMPTY_PATH) = 0
openat(12, ".", O_RDONLY|O_NOFOLLOW|O_PATH) = 26
newfstatat(26, "", {st_mode=S_IFDIR|S_ISGID|0770, st_size=4096, ...}, AT_EMPTY_PATH) = 0
newfstatat(25, "", {st_mode=S_IFREG|0600, st_size=45056, ...}, AT_EMPTY_PATH) = 0
munmap(0x7f4b82f0c000, 696) = 0
mmap(NULL, 36864, PROT_READ|PROT_WRITE, MAP_SHARED, 25, 0x2000) = 0x7f4b82e63000
openat(AT_FDCWD, "/proc/self/fd/26", O_RDONLY|O_DIRECTORY) = -1 EACCES (Permission denied)
close(26) = 0
...
We can see that the Samba process switches the effective user and group to the authenticated user (me) and then performs actions on the file system. We can see in the trace that the openat
syscall fails with the EACCESS
error. I double-checked all file system permissions and made me
the owner of the winshare folder. Still, the EACCESS
error persisted. I was so confused that I even wrote a simple app to reproduce the syscalls above:
#include <iostream> #include <array> #include <sstream> #include <unistd.h> #include <grp.h> #include <fcntl.h> #include <errno.h> int main(int argc, char* argv[]) { std::cout << "euid: " << ::geteuid() << std::endl; std::cout << "egid: " << ::getegid() << std::endl; std::array<gid_t, 12> groups {956, 1000, 998, 991, 3, 90, 98, 1001, 962, 961, 150, 1002}; if (::setgroups(groups.size(), groups.data()) != 0) { std::cout << "setgroups error: " << errno << std::endl; return 2; } if (int err = ::setresgid(-1, 1000, -1); err != 0) { std::cout << "error: " << err << std::endl; return err; } if (int err = ::setresuid(1000, 1000, -1); err != 0) { std::cout << "error: " << err << std::endl; return err; } std::cout << "euid: " << ::geteuid() << std::endl; std::cout << "egid: " << ::getegid() << std::endl; if (int err = ::chdir("/mnt/data/winshare"); err != 0) { std::cout << "error: " << err << std::endl; return err; } std::array<char, 1024> cwd{}; if (::getcwd(cwd.data(), cwd.size()) == nullptr) { std::cout << "getcwd error: " << errno << std::endl; return -1; } std::cout << "cwd: " << cwd.data() << std::endl; // strace: openat(AT_FDCWD, ".", O_RDONLY|O_NOFOLLOW|O_PATH) = 12 if (int fd = ::openat64(AT_FDCWD, ".", O_RDONLY|O_NOFOLLOW|O_PATH); fd != -1) { std::cout << "Folder opened: " << fd << std::endl; // strace: openat(AT_FDCWD, "/proc/self/fd/26", O_RDONLY|O_DIRECTORY) = -1 EACCES (Permission denied) std::stringstream ss{}; ss << "/proc/self/fd/" << fd; auto proc_path = ss.str(); if (int proc_fd = ::openat64(AT_FDCWD, proc_path.c_str(), O_RDONLY|O_DIRECTORY); proc_fd != -1) { std:: cout << "Proc folder opened: " << proc_fd << std::endl; std::cin >> proc_path; ::close(proc_fd); } else { std::cout << "proc openat error: " << errno << std::endl; } ::close(fd); return 0; } else { std::cout << "openat error: " << errno << std::endl; return -1; } }
As you may guess, there was no error when I ran it. I scratched my head, looking online for similar issues, but could find nothing. As I had a lot of pending work, I started using the \\mypc.local\me
share. Samba worked fine except for two issues: it was impossible to list the browseable shares from the Windows machines, and, secondly, the initial I/O requests over Samba were often very slow. Still, the initial problem was bugging me the most.
After a few weeks, I finally found some time to give it a second try.
Filesystem security checks are not the only ones
I again struggled with Samba config (I read the whole smb.conf man page! :)), but ended with strace. As I had my sample application working, I started comparing the process properties in the proc file system. And there, I discovered the attr folder, which stores various security-related attributes. The /proc/{pid}/attr/current
file for my sample process contained unconfined
while for the smbd process, its content was smbd (enforce)
. After searching through manual pages and Arch Linux wiki, I found that those settings come from the AppArmor module. The aa-status command only confirmed that:
# aa-status
apparmor module is loaded.
80 profiles are loaded.
77 profiles are in enforce mode.
...
samba-dcerpcd
samba-rpcd
samba-rpcd-classic
samba-rpcd-spoolss
smbd
...
9 processes are in enforce mode.
/usr/bin/avahi-daemon (1479) avahi-daemon
/usr/bin/avahi-daemon (1489) avahi-daemon
/usr/bin/dnsmasq (1698) dnsmasq
/usr/bin/dnsmasq (1699) dnsmasq
/usr/bin/nmbd (1778) nmbd
/usr/bin/smbd (1785) smbd
/usr/bin/smbd (1787) smbd
/usr/bin/smbd (1788) smbd
/usr/bin/smbd (5225) smbd
...
Now, I needed to locate the problematic AppArmor profiles. But how to find their names? Obviously, in the system journal! I should have checked it in the very beginning. I was studying the smb unit logs while all the details were at my fingertips:
# journalctl -fx
...
lis 06 12:19:14 mypc audit[5535]: AVC apparmor="DENIED" operation="open" profile="smbd" name="/mnt/data/winshare/" pid=5535 comm="smbd" requested_mask="r" denied_mask="r" fsuid=1000 ouid=1000
...
The smbd
profile, defined in /etc/apparmor.d/usr.sbin.smbd
, denies access to my target folder. Let’s have a look at it (I left only the essential parts):
abi <abi/3.0>,
include <tunables/global>
profile smbd /usr/{bin,sbin}/smbd {
...
/etc/mtab r,
/etc/netgroup r,
/etc/printcap r,
/etc/samba/* rwk,
@{PROC}/@{pid}/mounts r,
@{PROC}/sys/kernel/core_pattern r,
/usr/lib*/samba/vfs/*.so mr,
/usr/lib*/samba/auth/*.so mr,
/usr/lib*/samba/charset/*.so mr,
/usr/lib*/samba/gensec/*.so mr,
/usr/lib*/samba/pdb/*.so mr,
/usr/lib*/samba/{,samba/}samba-bgqd Px -> samba-bgqd,
/usr/lib*/samba/{,samba/}samba-dcerpcd Px -> samba-dcerpcd,
/usr/lib*/samba/{lowcase,upcase,valid}.dat r,
/usr/lib/@{multiarch}/samba/*.so{,.[0-9]*} mr,
/usr/lib/@{multiarch}/samba/**/ r,
/usr/lib/@{multiarch}/samba/**/*.so{,.[0-9]*} mr,
/usr/share/samba/** r,
/usr/{bin,sbin}/smbd mr,
/usr/{bin,sbin}/smbldap-useradd Px,
/var/cache/samba/** rwk,
/var/{cache,lib}/samba/printing/printers.tdb mrw,
/var/lib/samba/** rwk,
/var/lib/sss/pubconf/kdcinfo.* r,
@{run}/dbus/system_bus_socket rw,
@{run}/smbd.pid rwk,
@{run}/samba/** rk,
@{run}/samba/ncalrpc/ rw,
@{run}/samba/ncalrpc/** rw,
@{run}/samba/smbd.pid rw,
/var/spool/samba/** rw,
@{HOMEDIRS}/** lrwk,
/var/lib/samba/usershares/{,**} lrwk,
# Permissions for all configured shares (file autogenerated by
# update-apparmor-samba-profile on service startup on Debian and openSUSE)
include if exists <samba/smbd-shares>
include if exists <local/usr.sbin.smbd-shares>
# Site-specific additions and overrides. See local/README for details.
include if exists <local/usr.sbin.smbd>
Now, all is clear. AppArmor adds MAC (Mandatory Access Control) to the Samba process and interferes with the file system access checks. My share path (/mnt/data/winshare
) was not in the AppArmor profile; thus, access was denied. I believe that Debian and openSUSE users might not experience this problem thanks to the update-apparmor-samba-profile
script, but I haven’t had a chance to check it. Anyway, the solution for me was to create /etc/apparmor.d/local/usr.sbin.smbd-shares
with the missing access rights (I will have more shares from the data drive, so I just gave access to /mnt/data
).
While testing my shares with the system journal monitored, I discovered some more rules missing in the default AppArmor profiles. And I found that I wasn’t the only one with this problem. Inglebard reported a very similar issue and provided updates to the rules that worked for him. I added a comment with my findings. Finally, below are the updates that fixed all my problems with Samba.
$ cat /etc/apparmor.d/local/usr.sbin.smbd-shares
/mnt/data/** lrwk,
$ cat /etc/apparmor.d/local/samba-dcerpcd
# Site-specific additions and overrides for 'samba-dcerpcd'
@{run}/samba-dcerpcd.pid lrwk,
/var/cache/samba/** rwk,
@{HOMEDIRS}/** lrwk,
/var/lib/samba/usershares/{,**} lrwk,
include if exists <samba/smbd-shares>
include if exists <usr.sbin.smbd-shares>
$ cat /etc/apparmor.d/local/samba-rpcd
# Site-specific additions and overrides for 'samba-rpcd'
/var/cache/samba/** rwk,
@{HOMEDIRS}/** lrwk,
/var/lib/samba/usershares/{,**} lrwk,
include if exists <samba/smbd-shares>
include if exists <usr.sbin.smbd-shares>
$ cat /etc/apparmor.d/local/samba-rpcd-classic
# Site-specific additions and overrides for 'samba-rpcd-classic'
/var/cache/samba/** rwk,
/dev/urandom rwk,
@{HOMEDIRS}/** lrwk,
/var/lib/samba/usershares/{,**} lrwk,
include if exists <samba/smbd-shares>
include if exists <usr.sbin.smbd-shares>