[Security Advisory] CVE-2026-31431 “Copy Fail” — Impact Clarification for QNAP NAS Users

QNAP is currently investigating CVE-2026-31431, also known as Copy Fail, and we would like to provide some clarification for users who may be concerned about its impact on QNAP NAS devices.

In short, most QNAP NAS models are not affected by this vulnerability.

This issue affects only certain ARM-based QNAP NAS models running specific Linux kernel versions. According to our current assessment:

  • All x86-based QNAP NAS models are not affected.
  • ARM-based NAS models running QTS 4.x are not affected.
  • The issue only applies to specific ARM-based NAS models running affected kernel versions.

Please refer to the official QNAP Security Advisory for the latest information:

https://www.qnap.com/go/security-advisory/qsa-26-16

About this vulnerability

This vulnerability is a local privilege escalation issue.

This means that an attacker would first need to be able to run code on the NAS as a regular, non-administrator user before attempting to exploit the vulnerability. It is not a vulnerability that can be directly exploited from the internet without first gaining some level of local access.

For QNAP NAS devices, SSH and Telnet access are limited to administrator-group users by default. However, users should still review their system and application exposure, especially if they are running services or containers that can be accessed by other users or from external networks.

Recommended actions

For general risk reduction, we recommend the following:

  • Do not grant shell access to non-administrator users unless absolutely necessary.
  • Only run container images from trusted sources.
  • Review Container Station settings and avoid allowing unnecessary user access to containers.
  • Keep applications, containers, and services updated.
  • Disable unused services and applications.
  • If the built-in Web Server is not actively being used, consider disabling it from Control Panel > Web Server.
  • Keep the NAS behind a firewall and avoid exposing it directly to the internet.
  • Follow the official security advisory and install security updates once they become available.

QNAP is working on a security update and will update the advisory when more information or fixes are available.

If you have a specific system configuration that you are concerned about, please contact QNAP Support for further assistance.

For your security, please do not post sensitive information publicly in the forum, including public IP addresses, usernames, device serial numbers, full logs, or detailed system configuration.

— QNAP Community Team
Based on information from the QNAP Product Security Incident Response Team

Question on containers: Many containers are running under Linux versions like Alpine or BusyBox or similar. Since the containers are running a “different” Linux that the QNAP, is there a vulnerability in those containers? Or is that not the case?

Containers running on a QNAP NAS share the same kernel as other processes, so if your NAS is affected, we recommend paying extra attention to this.

OK. So if my NAS is not affected then my containers will be OK as well - yes?

Thanks to AI that I can have a test script quickly.

I am random picking a h874 with QTS 5.2.8 (2025/12/25) and run this script generated from Claude, and double checked by Codex to verify if the entrypoint exist on a QTS:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""CVE-2026-31431 surface check. Python 2.7 / 3.x compatible. Bind-only, safe."""
from __future__ import print_function
import os, socket, sys, errno

AF_ALG, SOCK_SEQPACKET = 38, 5

def in_container():
    if os.path.exists("/.dockerenv"):
        return True
    try:
        with open("/proc/1/cgroup") as f:
            d = f.read()
        return any(k in d for k in ("docker", "lxc", "kubepods", "containerd"))
    except (IOError, OSError):
        return False

def kernel_info():
    u = os.uname()
    # u: (sysname, nodename, release, version, machine)
    print("[*] Kernel:    {0} ({1})".format(u[2], u[4]))
    print("[*] Container: {0}".format(in_container()))

def kconfig_probe():
    paths = ["/proc/config.gz", "/boot/config-" + os.uname()[2]]
    keys = ("CONFIG_CRYPTO_USER_API_AEAD", "CONFIG_CRYPTO_USER_API",
            "CONFIG_CRYPTO_AUTHENC")
    for p in paths:
        if not os.path.exists(p):
            continue
        try:
            if p.endswith(".gz"):
                import gzip
                f = gzip.open(p, "rt")
            else:
                f = open(p, "r")
            data = f.read()
            f.close()
        except Exception as e:
            print("[!] kconfig read fail {0}: {1}".format(p, e))
            continue
        print("[*] kconfig source: {0}".format(p))
        for line in data.splitlines():
            for k in keys:
                if line.startswith(k + "=") or line.startswith("# " + k + " "):
                    print("    {0}".format(line))
        return
    print("[*] kconfig: not exposed (no /proc/config.gz, no /boot/config-*)")

def module_state():
    loaded = False
    try:
        with open("/proc/modules") as f:
            for line in f:
                if line.startswith("algif_aead "):
                    loaded = True
                    break
    except (IOError, OSError) as e:
        print("[!] /proc/modules: {0}".format(e))
    sysmod = os.path.isdir("/sys/module/algif_aead")
    print("[*] algif_aead loaded:  {0}".format(loaded))
    print("[*] /sys/module/algif_aead present: {0}".format(sysmod))
    bl = []
    for d in ("/etc/modprobe.d", "/run/modprobe.d", "/lib/modprobe.d"):
        if not os.path.isdir(d):
            continue
        try:
            entries = os.listdir(d)
        except OSError:
            continue
        for fn in entries:
            full = os.path.join(d, fn)
            try:
                with open(full) as f:
                    if "algif_aead" in f.read():
                        bl.append(full)
            except (IOError, OSError):
                pass
    print("[*] blacklist refs:     {0}".format(bl if bl else "none"))
    return loaded, sysmod

def afalg_bind_probe():
    try:
        s = socket.socket(AF_ALG, SOCK_SEQPACKET, 0)
    except socket.error as e:
        en = e.args[0] if e.args else 0
        print("[+] AF_ALG socket() blocked: errno={0} ({1}) {2}".format(
            en, errno.errorcode.get(en, "?"), os.strerror(en) if en else ""))
        return False
    try:
        s.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
        print("[!] authencesn bind SUCCEEDED -- vulnerable surface EXPOSED")
        s.close()
        return True
    except socket.error as e:
        en = e.args[0] if e.args else 0
        print("[+] authencesn bind failed: errno={0} ({1}) {2}".format(
            en, errno.errorcode.get(en, "?"), os.strerror(en) if en else ""))
        s.close()
        return False

def userns_state():
    for p in ("/proc/sys/kernel/unprivileged_userns_clone",
              "/proc/sys/user/max_user_namespaces",
              "/proc/sys/kernel/apparmor_restrict_unprivileged_userns"):
        if os.path.exists(p):
            try:
                with open(p) as f:
                    print("[*] {0} = {1}".format(p, f.read().strip()))
            except (IOError, OSError):
                pass

def main():
    kernel_info()
    kconfig_probe()
    loaded, sysmod = module_state()
    exposed = afalg_bind_probe()
    userns_state()
    print("")
    if exposed:
        print("VERDICT: VULNERABLE attack surface present.")
        if in_container():
            print("         In container -> Part 2 (host page-cache write) reachable.")
        sys.exit(2)
    elif not (loaded or sysmod):
        print("VERDICT: Module absent and bind blocked.")
        sys.exit(0)
    else:
        print("VERDICT: Module present but bind blocked -- review.")
        sys.exit(1)

if __name__ == "__main__":
    main()

And the result:

From host:

[qadmin@874QTS ~]$ uname -a
Linux 874QTS 5.10.60-qnap #1 SMP Thu Dec 25 01:08:48 CST 2025 x86_64 GNU/Linux
[qadmin@874QTS ~]$ python cpftest.py 
[*] Kernel:    5.10.60-qnap (x86_64)
[*] Container: False
[*] kconfig: not exposed (no /proc/config.gz, no /boot/config-*)
[*] algif_aead loaded:  False
[*] /sys/module/algif_aead present: False
[*] blacklist refs:     none
[+] AF_ALG socket() blocked: errno=97 (EAFNOSUPPORT) Address family not supported by protocol
[*] /proc/sys/user/max_user_namespaces = 127243

VERDICT: Module absent and bind blocked.

From a python container inside same NAS:

root@1ebc107603de:/home# uname -a
Linux 1ebc107603de 5.10.60-qnap #1 SMP Thu Dec 25 01:08:48 CST 2025 x86_64 GNU/Linux
root@1ebc107603de:/home# python cpftest.py 
[*] Kernel:    5.10.60-qnap (x86_64)
[*] Container: True
[*] kconfig: not exposed (no /proc/config.gz, no /boot/config-*)
[*] algif_aead loaded:  False
[*] /sys/module/algif_aead present: False
[*] blacklist refs:     none
[+] AF_ALG socket() blocked: errno=97 (EAFNOSUPPORT) Address family not supported by protocol
[*] /proc/sys/user/max_user_namespaces = 127243

VERDICT: Module absent and bind blocked.

So it seems OK even using older version of QTS.

Note: I am not pro in the security team. Just sharing my thoughts.

It’s funny. I’ve seen people complaining about how QNAP uses old versions of this library or that, etc. Yet, it’s the newer version of the Linux kernel that has this vulnerability!