- From 1d7a12676c246a3955f926e461ee62a33ef0756b Mon Sep 17 00:00:00 2001
- From: Roland Mainz <roland.mainz@nrubsig.org>
- Date: Thu, 28 Sep 2023 14:39:06 +0200
- Subject: [PATCH] nfs_mount: Do not pass raw IPv6 addresses with colons to the
- kernel
- nfs_mount.exe should not pass raw IPv6 addresses to the kernel, as
- ':' are invalid in UNC paths and cause weired errors.
- Microsoft solved this problem by providing a transcription method to
- represent an IPv6 address in the form of a domain name that can be
- used in UNC paths, e.g. "[fe80::219:99ff:feae:73ce]" should be turned
- into "fe80--219-99ff-feae-73ce.ipv6-literal.net"
- See https://en.wikipedia.org/wiki/IPv6_address#Literal_IPv6_addresses_in_UNC_path_names
- for details.
- The patch also updates the help message to explain the transcription
- for raw IPv6 addresses.
- Reviewed-by: Joshuah Hurst <joshhurst@gmail.com>
- Reviewed-by: Cedric Blancher <cedric.blancher@gmail.com>
- Signed-off-by: Cedric Blancher <cedric.blancher@gmail.com>
- ---
- daemon/nfs41_server.c | 19 +++++++
- mount/mount.c | 114 +++++++++++++++++++++++++++++++++++++-----
- 2 files changed, 120 insertions(+), 13 deletions(-)
- diff --git a/daemon/nfs41_server.c b/daemon/nfs41_server.c
- index ef8f0ac..b4fdba2 100644
- --- a/daemon/nfs41_server.c
- +++ b/daemon/nfs41_server.c
- @@ -22,6 +22,7 @@
- #include <Windows.h>
- #include <strsafe.h>
- #include <stdio.h>
- +#include <ctype.h>
- #include "wintirpc.h"
- #include "rpc/rpc.h"
- @@ -283,12 +284,30 @@ int nfs41_server_resolve(
- struct netconfig *nconf;
- struct netbuf addr;
- char *netid, *uaddr;
- + const char *s;
- dprintf(SRVLVL, "--> nfs41_server_resolve(%s:%u)\n",
- hostname, port);
- addrs->count = 0;
- + /*
- + * Harden against garbage
- + * (originally added to avoid ':' characters (for example from
- + * raw IPv6 address strings), which are illegal in UNC paths)
- + * ('_' is an extention as SystemV and Windows allow this in
- + * /etc/hosts & /etc/inet/hosts)
- + */
- +#define isvalidhostnamechar(c) \
- + (isalnum(c) || ((c) == '.') || ((c) == '-') || ((c) == '_'))
- + for (s=hostname ; *s != '\0' ; s++) {
- + if (!isvalidhostnamechar(*s)) {
- + eprintf("nfs41_server_resolve: Invalid hostname '%s'\n", hostname);
- + status = ERROR_BAD_NET_NAME;
- + goto out;
- + }
- + }
- +
- StringCchPrintfA(service, 16, "%u", port);
- /* request a list of tcp addrs for the given hostname,port */
- diff --git a/mount/mount.c b/mount/mount.c
- index 48c7c88..f2d5650 100644
- --- a/mount/mount.c
- +++ b/mount/mount.c
- @@ -54,13 +54,13 @@ void PrintErrorMessage(
- static VOID PrintUsage(LPTSTR pProcess)
- {
- _tprintf(TEXT("Usage: %s [options] <drive letter|*> <hostname>:<path>\n")
- - TEXT("Options:\n")
- + TEXT("* Options:\n")
- TEXT("\t-h\thelp\n")
- TEXT("\t-d\tunmount\n")
- TEXT("\t-f\tforce unmount if the drive is in use\n")
- TEXT("\t-p\tmake the mount persist over reboots\n")
- TEXT("\t-o <comma-separated mount options>\n")
- - TEXT("Mount options:\n")
- + TEXT("* Mount options:\n")
- TEXT("\tro\tmount as read-only\n")
- TEXT("\tport=#\tTCP port to use (defaults to 2049)\n")
- TEXT("\trsize=#\tread buffer size in bytes\n")
- @@ -68,7 +68,12 @@ static VOID PrintUsage(LPTSTR pProcess)
- TEXT("\tsec=krb5:krb5i:krb5p\tspecify gss security flavor\n")
- TEXT("\twritethru\tturns off rdbss caching for writes\n")
- TEXT("\tnocache\tturns off rdbss caching\n")
- - TEXT("\ttimeout=#\tspecify upcall timeout value in seconds (default 120s)\n"),
- + TEXT("\ttimeout=#\tspecify upcall timeout value in seconds (default 120s)\n")
- + TEXT("* Hostname:\n")
- + TEXT("\tDNS name, or hostname in domain\n")
- + TEXT("\tentry in C:\\Windows\\System32\\drivers\\etc\\hosts\n")
- + TEXT("\tIPv4 address\n")
- + TEXT("\tIPv6 address within '[', ']' (will be converted to *.ipv6-literal.net)\n"),
- pProcess);
- }
- @@ -210,6 +215,7 @@ static void ConvertUnixSlashes(
- static DWORD ParseRemoteName(
- IN LPTSTR pRemoteName,
- IN OUT PMOUNT_OPTION_LIST pOptions,
- + OUT LPTSTR pParsedRemoteName,
- OUT LPTSTR pConnectionName,
- IN size_t cchConnectionLen)
- {
- @@ -217,27 +223,31 @@ static DWORD ParseRemoteName(
- LPTSTR pEnd;
- int port = 0;
- PFILE_FULL_EA_INFORMATION port_option_val;
- + wchar_t remotename[MAX_PATH];
- + wchar_t *premotename = remotename;
- wchar_t srvname[MAX_PATH+1+32]; /* sizeof(hostname+'@'+integer) */
- -
- +
- + result = StringCchCopy(premotename, MAX_PATH, pRemoteName);
- +
- /*
- * gisburn: Fixme: Implement nfs://-URLS per RFC 2224 ("NFS URL
- * SCHEME", see https://www.rfc-editor.org/rfc/rfc2224.html),
- * including port support (nfs://hostname@port/path/...)
- */
- - if (!wcsncmp(pRemoteName, TEXT("nfs://"), 6)) {
- + if (!wcsncmp(premotename, TEXT("nfs://"), 6)) {
- _ftprintf(stderr, TEXT("nfs://-URLs not supported yet.\n"));
- result = ERROR_NOT_SUPPORTED;
- goto out;
- }
- - ConvertUnixSlashes(pRemoteName);
- + ConvertUnixSlashes(premotename);
- /*
- * Remote hostname should not contain a '@' since we use this
- * to communicate the NFSv4 port number below
- * Use $ nfs_mount.exe -o port=portnumber ... # instead
- */
- - if (_tcsrchr(pRemoteName, TEXT('@'))) {
- + if (_tcsrchr(premotename, TEXT('@'))) {
- _ftprintf(stderr, TEXT("Remote path should not contain '@', ")
- TEXT("use -o port=tcpportnum.\n"));
- result = ERROR_BAD_ARGUMENTS;
- @@ -259,7 +269,7 @@ static DWORD ParseRemoteName(
- }
- /* fail if the server name doesn't end with :\ */
- - pEnd = _tcsrchr(pRemoteName, TEXT(':'));
- + pEnd = _tcsrchr(premotename, TEXT(':'));
- if (pEnd == NULL || pEnd[1] != TEXT('\\')) {
- _ftprintf(stderr, TEXT("Failed to parse the remote path. ")
- TEXT("Expected 'hostname:\\path'.\n"));
- @@ -270,9 +280,84 @@ static DWORD ParseRemoteName(
- ++pEnd;
- /*
- - * ALWAYS add port number to hostname, so UNC paths use it too
- + * Make sure that we do not pass raw IPv6 addresses to the kernel.
- + *
- + * UNC paths do not allow ':' characters, so passing a raw IPv6
- + * address to the kernel is both illegal, and causes havoc.
- + *
- + * Microsoft solved this problem by providing a transcription
- + * method to represent an IPv6 address in the form of a domain
- + * name that can be used in UNC paths, e.g.
- + * "[fe80::219:99ff:feae:73ce]" should be turned into
- + * "fe80--219-99ff-feae-73ce.ipv6-literal.net"
- + *
- + * See https://en.wikipedia.org/wiki/IPv6_address#Literal_IPv6_addresses_in_UNC_path_names
- + * for details
- + */
- + if (premotename[0] == TEXT('[')) {
- + size_t len = wcslen(premotename);
- + size_t i;
- + wchar_t c;
- +
- + /* Check for minimum length and trailing ']' */
- + if ((len < 4) || (premotename[len-1] != TEXT(']'))) {
- + _ftprintf(stderr, TEXT("Failed to parse raw IPv6 address,")
- + TEXT(" trailing ']' is missing, ")
- + TEXT("or address string too short.\n"));
- + result = ERROR_BAD_ARGUMENTS;
- + goto out;
- + }
- +
- + /* Skip '[', stomp ']' */
- + premotename[len-1] = TEXT('\0');
- + premotename++;
- + len -= 2;
- +
- + /* Check whether this is a valid IPv6 address */
- + for (i=0 ; i < len ; i++) {
- + c = premotename[i];
- + if (!(iswxdigit(c) || (c == TEXT(':')))) {
- + _ftprintf(stderr, TEXT("Failed to parse raw IPv6 ")
- + TEXT("address, illegal character '%c' found.\n"),
- + c);
- + result = ERROR_BAD_ARGUMENTS;
- + goto out;
- + }
- + }
- +
- + for (i = 0 ; i < len ; i++) {
- + /* IPv6 separator */
- + if (premotename[i] == TEXT(':'))
- + premotename[i] = TEXT('-');
- + /* zone index */
- + else if (premotename[i] == TEXT('%'))
- + premotename[i] = TEXT('s');
- + }
- +
- + /*
- + * 1. Append .ipv6-literal.net to hostname
- + * 2. ALWAYS add port number to hostname, so UNC paths use it
- + * too
- + */
- + (void)swprintf(srvname, sizeof(srvname),
- + TEXT("%s.ipv6-literal.net@%d"), premotename, port);
- + }
- + else {
- + /* ALWAYS add port number to hostname, so UNC paths use it too */
- + (void)swprintf(srvname, sizeof(srvname), TEXT("%s@%d"),
- + premotename, port);
- + }
- +
- + /*
- + * Safeguard against ':' in UNC paths, e.g if we pass raw IPv6
- + * address without ':', or just random garbage
- */
- - (void)swprintf(srvname, sizeof(srvname), TEXT("%s@%d"), pRemoteName, port);
- + if (wcschr(srvname, TEXT(':'))) {
- + _ftprintf(stderr,
- + TEXT("Illegal ':' character hostname '%s'.\n"), srvname);
- + result = ERROR_BAD_ARGUMENTS;
- + goto out;
- + }
- if (!InsertOption(TEXT("srvname"), srvname, pOptions) ||
- !InsertOption(TEXT("mntpt"), *pEnd ? pEnd : TEXT("\\"), pOptions)) {
- @@ -303,6 +388,8 @@ static DWORD ParseRemoteName(
- if (*pEnd)
- result = StringCchCat(pConnectionName, cchConnectionLen, pEnd);
- + result = StringCchCopy(pParsedRemoteName, cchConnectionLen, srvname);
- +
- out:
- return result;
- }
- @@ -315,11 +402,12 @@ static DWORD DoMount(
- {
- DWORD result = NO_ERROR;
- TCHAR szExisting[MAX_PATH];
- + TCHAR szParsedRemoteName[MAX_PATH];
- TCHAR szRemoteName[MAX_PATH];
- DWORD dwLength;
- *szRemoteName = TEXT('\0');
- - result = ParseRemoteName(pRemoteName, pOptions, szRemoteName, MAX_PATH);
- + result = ParseRemoteName(pRemoteName, pOptions, szParsedRemoteName, szRemoteName, MAX_PATH);
- if (result)
- goto out;
- @@ -361,8 +449,8 @@ static DWORD DoMount(
- szConnection, &ConnectSize, &ConnectResult);
- if (result == NO_ERROR)
- - _tprintf(TEXT("Successfully mounted %s to drive %s\n"),
- - pRemoteName, szConnection);
- + _tprintf(TEXT("Successfully mounted '%s' to drive '%s'\n"),
- + szParsedRemoteName, szConnection);
- else
- _ftprintf(stderr, TEXT("WNetUseConnection(%s, %s) ")
- TEXT("failed with error code %u.\n"),
- --
- 2.39.0
msnfs41client: nfs_mount: Do not pass raw IPv6 addresses with colons to the kernel
Posted by Anonymous on Thu 28th Sep 2023 13:52
raw | new post
Submit a correction or amendment below (click here to make a fresh posting)
After submitting an amendment, you'll be able to view the differences between the old and new posts easily.