[PATCH 19/22] xfs_scrub: warn about normalized Unicode name collisions

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Darrick J. Wong <darrick.wong@xxxxxxxxxx>

Iterate all directory and xattr names to look for name collisions
amongst Unicode normalized names.  This is generally a sign of buggy
programs or malicious duplicate files.

Signed-off-by: Darrick J. Wong <darrick.wong@xxxxxxxxxx>
---
 configure.ac            |    4 
 debian/control          |    2 
 include/builddefs.in    |    3 
 m4/Makefile             |    1 
 m4/package_libcdev.m4   |   13 +
 m4/package_unistring.m4 |   19 ++
 scrub/Makefile          |   18 +-
 scrub/common.c          |   20 ++
 scrub/common.h          |    3 
 scrub/phase5.c          |   15 +
 scrub/unicrash.c        |  484 +++++++++++++++++++++++++++++++++++++++++++++++
 scrub/unicrash.h        |   38 ++++
 12 files changed, 616 insertions(+), 4 deletions(-)
 create mode 100644 m4/package_unistring.m4
 create mode 100644 scrub/unicrash.c
 create mode 100644 scrub/unicrash.h


diff --git a/configure.ac b/configure.ac
index 8b65cf5..91fba71 100644
--- a/configure.ac
+++ b/configure.ac
@@ -124,6 +124,9 @@ AC_PACKAGE_NEED_UUIDCOMPARE
 AC_PACKAGE_NEED_PTHREAD_H
 AC_PACKAGE_NEED_PTHREADMUTEXINIT
 
+AC_PACKAGE_WANT_UNINORM_H
+AC_PACKAGE_WANT_U8_NORMALIZE
+
 AC_HAVE_FADVISE
 AC_HAVE_MADVISE
 AC_HAVE_MINCORE
@@ -148,6 +151,7 @@ AC_HAVE_OPENAT
 AC_HAVE_FSTATAT
 AC_HAVE_SG_IO
 AC_HAVE_HDIO_GETGEO
+AC_HAVE_ATTR_ROOT
 
 if test "$enable_blkid" = yes; then
 AC_HAVE_BLKID_TOPO
diff --git a/debian/control b/debian/control
index ad81662..c255ae6 100644
--- a/debian/control
+++ b/debian/control
@@ -3,7 +3,7 @@ Section: admin
 Priority: optional
 Maintainer: XFS Development Team <linux-xfs@xxxxxxxxxxxxxxx>
 Uploaders: Nathan Scott <nathans@xxxxxxxxxx>, Anibal Monsalve Salazar <anibal@xxxxxxxxxx>
-Build-Depends: uuid-dev, dh-autoreconf, debhelper (>= 5), gettext, libtool, libreadline-gplv2-dev | libreadline5-dev, libblkid-dev (>= 2.17), linux-libc-dev
+Build-Depends: uuid-dev, dh-autoreconf, debhelper (>= 5), gettext, libtool, libreadline-gplv2-dev | libreadline5-dev, libblkid-dev (>= 2.17), linux-libc-dev, libunistring-dev
 Standards-Version: 3.9.1
 Homepage: http://xfs.org/
 
diff --git a/include/builddefs.in b/include/builddefs.in
index c90c76f..2cde2dc 100644
--- a/include/builddefs.in
+++ b/include/builddefs.in
@@ -35,6 +35,8 @@ LIBTERMCAP = @libtermcap@
 LIBEDITLINE = @libeditline@
 LIBREADLINE = @libreadline@
 LIBBLKID = @libblkid@
+LIBUNISTRING = @libunistring@
+HAVE_LIBUNISTRING = @have_libunistring@
 LIBXFS = $(TOPDIR)/libxfs/libxfs.la
 LIBXCMD = $(TOPDIR)/libxcmd/libxcmd.la
 LIBXLOG = $(TOPDIR)/libxlog/libxlog.la
@@ -118,6 +120,7 @@ HAVE_OPENAT = @have_openat@
 HAVE_FSTATAT = @have_fstatat@
 HAVE_SG_IO = @have_sg_io@
 HAVE_HDIO_GETGEO = @have_hdio_getgeo@
+HAVE_ATTR_ROOT = @have_attr_root@
 
 GCCFLAGS = -funsigned-char -fno-strict-aliasing -Wall
 #	   -Wbitwise -Wno-transparent-union -Wno-old-initializer -Wno-decl
diff --git a/m4/Makefile b/m4/Makefile
index d282f0a..8348325 100644
--- a/m4/Makefile
+++ b/m4/Makefile
@@ -19,6 +19,7 @@ LSRCFILES = \
 	package_libcdev.m4 \
 	package_pthread.m4 \
 	package_types.m4 \
+	package_unistring.m4 \
 	package_utilies.m4 \
 	package_uuiddev.m4 \
 	multilib.m4 \
diff --git a/m4/package_libcdev.m4 b/m4/package_libcdev.m4
index 8608d10..5d821f0 100644
--- a/m4/package_libcdev.m4
+++ b/m4/package_libcdev.m4
@@ -373,3 +373,16 @@ AC_DEFUN([AC_HAVE_HDIO_GETGEO],
        AC_MSG_RESULT(no))
     AC_SUBST(have_hdio_getgeo)
   ])
+
+#
+# Check if we have a ATTR_ROOT flag
+#
+AC_DEFUN([AC_HAVE_ATTR_ROOT],
+  [ AC_CHECK_DECL([ATTR_ROOT],
+       have_attr_root=yes,
+       [],
+       [#include <sys/types.h>
+        #include <attr/attributes.h>]
+       )
+    AC_SUBST(have_attr_root)
+  ])
diff --git a/m4/package_unistring.m4 b/m4/package_unistring.m4
new file mode 100644
index 0000000..9aaadfc
--- /dev/null
+++ b/m4/package_unistring.m4
@@ -0,0 +1,19 @@
+AC_DEFUN([AC_PACKAGE_WANT_UNINORM_H],
+  [ AC_CHECK_HEADERS(uninorm.h)
+    if test $ac_cv_header_uninorm_h = no; then
+	AC_CHECK_HEADERS(uninorm.h,, [
+	echo
+	echo 'WARNING: could not find a valid uninorm header.'])
+    fi
+  ])
+
+AC_DEFUN([AC_PACKAGE_WANT_U8_NORMALIZE],
+  [ AC_CHECK_LIB(unistring, u8_normalize,[
+	libunistring=-lunistring
+	have_libunistring=yes
+    ],[
+	echo
+	echo 'WARNING: xfs_scrub will not be built with Unicode libraries.'])
+    AC_SUBST(libunistring)
+    AC_SUBST(have_libunistring)
+  ])
diff --git a/scrub/Makefile b/scrub/Makefile
index a17dfa3..0fbcf6b 100644
--- a/scrub/Makefile
+++ b/scrub/Makefile
@@ -27,7 +27,8 @@ read_verify.h \
 repair.h \
 scrub.h \
 vfs.h \
-xfs.h
+xfs.h \
+unicrash.h
 
 CFILES = \
 ../libxfs/list_sort.c \
@@ -51,8 +52,8 @@ scrub.c \
 vfs.c \
 xfs.c
 
-LLDLIBS += $(LIBXCMD) $(LIBHANDLE) $(LIBPTHREAD)
-LTDEPENDENCIES += $(LIBXCMD) $(LIBHANDLE)
+LLDLIBS += $(LIBXCMD) $(LIBHANDLE) $(LIBPTHREAD) $(LIBUNISTRING)
+LTDEPENDENCIES += $(LIBXCMD) $(LIBHANDLE) $(LIBUNISTRING)
 LLDFLAGS = -static
 
 ifeq ($(HAVE_MALLINFO),yes)
@@ -71,8 +72,19 @@ ifeq ($(HAVE_HDIO_GETGEO),yes)
 LCFLAGS += -DHAVE_HDIO_GETGEO
 endif
 
+ifeq ($(HAVE_LIBUNISTRING),yes)
+CFILES += unicrash.c
+LCFLAGS += -DHAVE_LIBUNISTRING
+endif
+
+ifeq ($(HAVE_ATTR_ROOT),yes)
+LCFLAGS += -DHAVE_ATTR_ROOT
+endif
+
 default: depend $(LTCOMMAND)
 
+unicrash.o xfs.o: $(TOPDIR)/include/builddefs
+
 include $(BUILDRULES)
 
 install: default $(INSTALL_SCRUB)
diff --git a/scrub/common.c b/scrub/common.c
index 7ca0e78..02a8453 100644
--- a/scrub/common.c
+++ b/scrub/common.c
@@ -85,6 +85,26 @@ __str_errno(
 	pthread_mutex_unlock(&ctx->lock);
 }
 
+/* Print a warning string and whatever error is stored in errno. */
+void
+__str_errno_warn(
+	struct scrub_ctx	*ctx,
+	const char		*descr,
+	const char		*file,
+	int			line)
+{
+	char			buf[DESCR_BUFSZ];
+
+	pthread_mutex_lock(&ctx->lock);
+	fprintf(stderr, _("Warning: %s: %s."), descr,
+			strerror_r(errno, buf, DESCR_BUFSZ));
+	if (debug)
+		fprintf(stderr, _(" (%s line %d)"), file, line);
+	fprintf(stderr, "\n");
+	ctx->warnings_found++;
+	pthread_mutex_unlock(&ctx->lock);
+}
+
 /* Print an error string and some error text. */
 void
 __str_error(
diff --git a/scrub/common.h b/scrub/common.h
index 2e4ff05..6251c6d 100644
--- a/scrub/common.h
+++ b/scrub/common.h
@@ -41,6 +41,8 @@ void __record_repair(struct scrub_ctx *ctx, const char *descr, const char *file,
 		int line, const char *format, ...);
 void __record_preen(struct scrub_ctx *ctx, const char *descr, const char *file,
 		int line, const char *format, ...);
+void __str_errno_warn(struct scrub_ctx *, const char *descr, const char *file,
+		      int line);
 
 #define str_errno(ctx, str)		__str_errno(ctx, str, __FILE__, __LINE__)
 #define str_error(ctx, str, ...)	__str_error(ctx, str, __FILE__, __LINE__, __VA_ARGS__)
@@ -48,6 +50,7 @@ void __record_preen(struct scrub_ctx *ctx, const char *descr, const char *file,
 #define str_info(ctx, str, ...)		__str_info(ctx, str, __FILE__, __LINE__, __VA_ARGS__)
 #define record_repair(ctx, str, ...)	__record_repair(ctx, str, __FILE__, __LINE__, __VA_ARGS__)
 #define record_preen(ctx, str, ...)	__record_preen(ctx, str, __FILE__, __LINE__, __VA_ARGS__)
+#define str_errno_warn(ctx, str)	__str_errno_warn(ctx, str, __FILE__, __LINE__)
 #define dbg_printf(fmt, ...)		{if (debug > 1) {printf(fmt, __VA_ARGS__);}}
 
 /* Is this debug tweak enabled? */
diff --git a/scrub/phase5.c b/scrub/phase5.c
index e5a5835..9010a08 100644
--- a/scrub/phase5.c
+++ b/scrub/phase5.c
@@ -31,6 +31,7 @@
 #include "ioctl.h"
 #include "xfs_fs.h"
 #include "xfs.h"
+#include "unicrash.h"
 
 /* Phase 5: Check directory connectivity. */
 
@@ -38,6 +39,8 @@
  * Verify the connectivity of the directory tree.
  * We know that the kernel's open-by-handle function will try to reconnect
  * parents of an opened directory, so we'll accept that as sufficient.
+ *
+ * Check for potential Unicode collisions in names.
  */
 static int
 xfs_scrub_connections(
@@ -59,6 +62,11 @@ xfs_scrub_connections(
 			agno, agino);
 	background_sleep();
 
+	/* Warn about Unicode normalization problems in xattrs. */
+	moveon = unicrash_scan_fh_xattrs(ctx, descr, handle, bstat);
+	if (!moveon)
+		goto out;
+
 	/* Open the dir, let the kernel try to reconnect it to the root. */
 	if (S_ISDIR(bstat->bs_mode)) {
 		fd = xfs_open_handle(handle);
@@ -70,6 +78,13 @@ xfs_scrub_connections(
 		}
 	}
 
+	/* Warn about Unicode normalization problems in dirents. */
+	if (fd >= 0 && S_ISDIR(bstat->bs_mode)) {
+		moveon = unicrash_scan_dir(ctx, descr, fd, bstat->bs_size);
+		if (!moveon)
+			goto out;
+	}
+
 out:
 	if (fd >= 0)
 		close(fd);
diff --git a/scrub/unicrash.c b/scrub/unicrash.c
new file mode 100644
index 0000000..dfa8a0c
--- /dev/null
+++ b/scrub/unicrash.c
@@ -0,0 +1,484 @@
+/*
+ * Copyright (C) 2017 Oracle.  All Rights Reserved.
+ *
+ * Author: Darrick J. Wong <darrick.wong@xxxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it would be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write the Free Software Foundation,
+ * Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#include "libxfs.h"
+#include <dirent.h>
+#include <sys/statvfs.h>
+#include <sys/types.h>
+#ifdef HAVE_ATTR_ROOT
+# include <attr/attributes.h>
+#endif
+#include <unistr.h>
+#include <uninorm.h>
+#include "disk.h"
+#include "../repair/threads.h"
+#include "handle.h"
+#include "path.h"
+#include "read_verify.h"
+#include "bitmap.h"
+#include "vfs.h"
+#include "scrub.h"
+#include "common.h"
+#include "unicrash.h"
+
+/*
+ * Detect collisions of Unicode-normalized names.
+ *
+ * Record all the name->ino mappings in a directory/xattr, with a twist!
+ * The twist is that we perform unicode normalization on every name we
+ * see, so that we can warn about a directory containing more than one
+ * directory entries that normalize to the same Unicode string.  These
+ * entries are at best a sign of Unicode mishandling, or some sort of
+ * weird name substitution attack if the entries do not point to the
+ * same inode.  Warn if we see multiple dirents that do not all point to
+ * the same inode.
+ *
+ * For extended attributes we perform the same collision checks on the
+ * attribute, though any collision is enough to trigger a warning.
+ *
+ * We flag these collisions as warnings and not errors because XFS
+ * treats names as a sequence of arbitrary nonzero bytes.  While a
+ * Unicode collision is not technically a filesystem corruption, we
+ * ought to say something if there's a possibility for misleading a
+ * user.
+ *
+ * To normalize, we use Unicode NFKC.  We use the composing
+ * normalization mode (e.g. "E WITH ACUTE" instead of "E" then "ACUTE")
+ * because that's what W3C (and in general Linux) uses.  This enables us
+ * to detect multiple object names that normalize to the same name and
+ * could be confusing to users.  Furthermore, we use the compatibility
+ * mode to detect names with compatible but different code points to
+ * strengthen those checks.
+ */
+
+struct name_entry {
+	struct name_entry	*next;
+	xfs_ino_t		ino;
+	size_t			namelen;
+	uint8_t			name[0];
+};
+#define NAME_ENTRY_SZ(nl)	(sizeof(struct name_entry) + 1 + \
+				 (nl * sizeof(uint8_t)))
+
+struct unicrash {
+	bool			compare_ino;
+	size_t			nr_buckets;
+	struct name_entry	*buckets[0];
+};
+#define UNICRASH_SZ(nr)		(sizeof(struct unicrash) + \
+				 (nr * sizeof(struct name_entry)))
+
+/* Initialize the crash detector. */
+static struct unicrash *
+unicrash_init(
+	bool			compare_ino,
+	size_t			nr_buckets)
+{
+	struct unicrash		*p;
+
+	assert(nr_buckets > 0);
+	p = calloc(1, UNICRASH_SZ(nr_buckets));
+	if (!p)
+		return NULL;
+	p->nr_buckets = nr_buckets;
+	p->compare_ino = compare_ino;
+	return p;
+}
+
+/* Free the crash detector. */
+static void
+unicrash_free(
+	struct unicrash		*uc)
+{
+	struct name_entry	*ne;
+	struct name_entry	*x;
+	size_t			i;
+
+	for (i = 0; i < uc->nr_buckets; i++) {
+		for (ne = uc->buckets[i]; ne != NULL; ne = x) {
+			x = ne->next;
+			free(ne);
+		}
+	}
+	free(uc);
+}
+
+/* Steal the dirhash function from libxfs, avoid linking with libxfs. */
+
+#define rol32(x, y)		(((x) << (y)) | ((x) >> (32 - (y))))
+
+/*
+ * Implement a simple hash on a character string.
+ * Rotate the hash value by 7 bits, then XOR each character in.
+ * This is implemented with some source-level loop unrolling.
+ */
+xfs_dahash_t
+unicrash_hashname(
+	const uint8_t		*name,
+	size_t			namelen)
+{
+	xfs_dahash_t		hash;
+
+	/*
+	 * Do four characters at a time as long as we can.
+	 */
+	for (hash = 0; namelen >= 4; namelen -= 4, name += 4)
+		hash = (name[0] << 21) ^ (name[1] << 14) ^ (name[2] << 7) ^
+		       (name[3] << 0) ^ rol32(hash, 7 * 4);
+
+	/*
+	 * Now do the rest of the characters.
+	 */
+	switch (namelen) {
+	case 3:
+		return (name[0] << 14) ^ (name[1] << 7) ^ (name[2] << 0) ^
+		       rol32(hash, 7 * 3);
+	case 2:
+		return (name[0] << 7) ^ (name[1] << 0) ^ rol32(hash, 7 * 2);
+	case 1:
+		return (name[0] << 0) ^ rol32(hash, 7 * 1);
+	default: /* case 0: */
+		return hash;
+	}
+}
+
+/*
+ * Normalize a name according to Unicode NFKC normalization rules.
+ * Returns true if the name was already normalized.
+ */
+static bool
+unicrash_normalize(
+	const char		*in,
+	uint8_t			*out,
+	size_t			outlen)
+{
+	size_t			inlen = strlen(in);
+
+	assert(inlen <= outlen);
+	if (!u8_normalize(UNINORM_NFKC, (const uint8_t *)in, inlen,
+			out, &outlen)) {
+		/* Didn't normalize, just return the same buffer. */
+		memcpy(out, in, inlen + 1);
+		return true;
+	}
+	out[outlen] = 0;
+	return outlen == inlen ? memcmp(in, out, inlen) == 0 : false;
+}
+
+/*
+ * Return the input string with non-printing bytes escaped.
+ * Caller must free the buffer.
+ */
+static char *
+unicrash_escape(
+	const char		*in)
+{
+	char			*str;
+	const char		*p;
+	char			*q;
+	int			x;
+
+	str = malloc(strlen(in) * 4);
+	for (p = in, q = str; *p != '\0'; p++) {
+		if (isprint(*p)) {
+			*q = *p;
+			q++;
+		} else {
+			x = sprintf(q, "\\x%02x", *p);
+			q += x;
+		}
+	}
+	*q = '\0';
+	return str;
+}
+
+/* Complain about Unicode problems. */
+#define TOO_MANY_NORM_WARNINGS	10000
+static void
+unicrash_complain(
+	struct scrub_ctx	*ctx,
+	const char		*descr,
+	const char		*what,
+	bool			normal,
+	bool			unique,
+	const char		*name,
+	uint8_t			*uniname)
+{
+	static pthread_mutex_t	normlock = PTHREAD_MUTEX_INITIALIZER;
+	static unsigned int	normwarnings;
+	char			*bad1 = NULL;
+	char			*bad2 = NULL;
+
+	if (normal && unique)
+		return;
+
+	bad1 = unicrash_escape(name);
+	bad2 = unicrash_escape((char *)uniname);
+
+	if (!normal && (debug || verbose)) {
+		pthread_mutex_lock(&normlock);
+		if (normwarnings == TOO_MANY_NORM_WARNINGS) {
+			str_info(ctx, ctx->mntpoint,
+_("Filesystem has more than %u normalization warnings, shutting up."),
+					normwarnings);
+			normwarnings++;
+		} else if (normwarnings < TOO_MANY_NORM_WARNINGS) {
+			str_info(ctx, descr,
+_("Unicode name \"%s\" in %s should be normalized as \"%s\"."),
+					bad1, what, bad2);
+			normwarnings++;
+		}
+		pthread_mutex_unlock(&normlock);
+	}
+	if (!unique)
+		str_warn(ctx, descr,
+_("Duplicate normalized Unicode name \"%s\" found in %s."),
+				bad1, what);
+
+	free(bad1);
+	free(bad2);
+}
+#undef TOO_MANY_NORM_WARNINGS
+
+/*
+ * Try to add a name -> ino entry to the collision detector.  The name
+ * must be normalized according to Unicode NFKC normalization rules to
+ * detect byte-unique names that map to the same sequence of Unicode
+ * code points.
+ *
+ * This function returns true either if there was no previous mapping or
+ * there was a mapping that matched exactly.  It returns false if
+ * there is already a record with that name pointing to a different
+ * inode.
+ */
+static bool
+unicrash_add(
+	struct unicrash		*uc,
+	uint8_t			*name,
+	xfs_ino_t		ino)
+{
+	struct name_entry	*ne;
+	struct name_entry	*x;
+	struct name_entry	**nep;
+	size_t			namelen = u8_strlen(name);
+	size_t			bucket;
+	xfs_dahash_t		hash;
+
+	/* Do we already know about that name? */
+	hash = unicrash_hashname(name, namelen);
+	bucket = hash % uc->nr_buckets;
+	for (nep = &uc->buckets[bucket], ne = *nep; ne != NULL; ne = x) {
+		if (u8_strcmp(name, ne->name) == 0)
+			return uc->compare_ino ? ne->ino == ino : false;
+		nep = &ne->next;
+		x = ne->next;
+	}
+
+	/* Remember that name. */
+	x = malloc(NAME_ENTRY_SZ(namelen));
+	x->next = NULL;
+	x->ino = ino;
+	x->namelen = namelen;
+	memcpy(x->name, name, namelen + 1);
+	*nep = x;
+	return true;
+}
+
+/*
+ * Iterate a directory looking for collisions between the Unicode
+ * normalized names.
+ */
+bool
+unicrash_scan_dir(
+	struct scrub_ctx	*ctx,
+	const char		*descr,
+	int			fd,
+	off_t			dirsize)
+{
+	uint8_t			buf[NAME_MAX * 2];
+	DIR			*dir;
+	struct dirent		*dentry;
+	struct unicrash		*uc;
+	size_t			buckets;
+	bool			normal;
+	bool			unique;
+	int			new_fd;
+
+	new_fd = dup(fd);
+	if (new_fd < 0) {
+		str_errno_warn(ctx, descr);
+		goto out;
+	}
+
+	dir = fdopendir(new_fd);
+	if (!dir) {
+		str_errno_warn(ctx, descr);
+		goto out_close;
+	}
+	new_fd = -1;
+
+	/*
+	 * Assume 64 bytes per dentry, clamp buckets between 16 and 64k.
+	 * Same general idea as dir_hash_init in xfs_repair.
+	 */
+	buckets = dirsize / 64;
+	if (buckets > 65536)
+		buckets = 65536;
+	else if (buckets < 16)
+		buckets = 16;
+
+	uc = unicrash_init(true, buckets);
+	if (!uc) {
+		str_errno_warn(ctx, descr);
+		goto out_closedir;
+	}
+
+	while ((dentry = readdir(dir))) {
+		normal = unicrash_normalize(dentry->d_name, buf, NAME_MAX * 2);
+		unique = unicrash_add(uc, buf, dentry->d_ino);
+
+		unicrash_complain(ctx, descr, _("directory"),
+				normal, unique, dentry->d_name, buf);
+	}
+
+	unicrash_free(uc);
+out_closedir:
+	closedir(dir);
+out_close:
+	if (new_fd >= 0)
+		close(new_fd);
+out:
+	return true;
+}
+
+/* Routines to scan all of an inode's xattrs for Unicode problems. */
+
+#ifdef HAVE_ATTR_ROOT
+enum xfs_xattr_ns {
+	RXT_USER	= 0,
+	RXT_ROOT,
+	RXT_TRUST,
+	RXT_SECURE,
+	RXT_MAX,
+};
+
+static const int ns_to_flag[] = {
+	[RXT_USER]	= 0,
+	[RXT_ROOT]	= ATTR_ROOT,
+	[RXT_TRUST]	= ATTR_TRUST,
+	[RXT_SECURE]	= ATTR_SECURE,
+};
+
+static const char * const ns_to_str[] = {
+	[RXT_USER]	= "user",
+	[RXT_ROOT]	= "system",
+	[RXT_TRUST]	= "trust",
+	[RXT_SECURE]	= "secure",
+};
+
+/*
+ * Check all the xattr names in a particular namespace of a file handle
+ * for Unicode normalization problems or collisions.
+ */
+static bool
+unicrash_scan_fh_ns_xattrs(
+	struct scrub_ctx	*ctx,
+	const char		*descr,
+	struct xfs_handle	*handle,
+	struct xfs_bstat	*bstat,
+	enum xfs_xattr_ns	ns)
+{
+	struct attrlist_cursor	cur;
+	char			attrbuf[XFS_XATTR_LIST_MAX];
+	char			buf[NAME_MAX];
+	uint8_t			nbuf[NAME_MAX * 2];
+	struct attrlist		*attrlist = (struct attrlist *)attrbuf;
+	struct attrlist_ent	*ent;
+	struct unicrash		*uc;
+	bool			normal;
+	bool			unique;
+	int			i;
+	int			error;
+
+	/* Assume 16 buckets per attr extent will do. */
+	uc = unicrash_init(false, 16 * (1 + bstat->bs_aextents));
+	if (!uc) {
+		str_errno_warn(ctx, descr);
+		goto out;
+	}
+
+	memset(attrbuf, 0, XFS_XATTR_LIST_MAX);
+	memset(&cur, 0, sizeof(cur));
+	while ((error = attr_list_by_handle(handle, sizeof(*handle),
+			attrbuf, XFS_XATTR_LIST_MAX, ns_to_flag[ns],
+			&cur)) == 0) {
+
+		/* Examine the xattrs. */
+		for (i = 0; i < attrlist->al_count; i++) {
+			ent = ATTR_ENTRY(attrlist, i);
+			normal = unicrash_normalize(ent->a_name, nbuf,
+					NAME_MAX * 2);
+			unique = unicrash_add(uc, nbuf, 0);
+
+			snprintf(buf, NAME_MAX, "%s.%s", ns_to_str[ns],
+					ent->a_name);
+			unicrash_complain(ctx, descr, _("extended attribute"),
+					normal, unique, buf, nbuf);
+		}
+
+		if (!attrlist->al_more)
+			break;
+	}
+	/*
+	 * ATTR_TRUST doesn't currently work on Linux, and ignore
+	 * the file if the handle is now stale.
+	 */
+	if (error && ((ns == RXT_TRUST && errno == EINVAL) ||
+		      (errno == ESTALE)))
+		error = 0;
+	if (error)
+		str_errno_warn(ctx, descr);
+	unicrash_free(uc);
+out:
+	return true;
+}
+
+/*
+ * Check all the xattr names under a file handle for Unicode normalization
+ * problems or collisions.
+ */
+bool
+unicrash_scan_fh_xattrs(
+	struct scrub_ctx	*ctx,
+	const char		*descr,
+	struct xfs_handle	*handle,
+	struct xfs_bstat	*bstat)
+{
+	enum xfs_xattr_ns	i;
+	bool			moveon = true;
+
+	for (i = 0; i < RXT_MAX; i++) {
+		moveon = unicrash_scan_fh_ns_xattrs(ctx, descr, handle,
+				bstat, i);
+		if (!moveon)
+			break;
+	}
+	return moveon;
+}
+#endif /* HAVE_ATTR_ROOT */
diff --git a/scrub/unicrash.h b/scrub/unicrash.h
new file mode 100644
index 0000000..9caae39
--- /dev/null
+++ b/scrub/unicrash.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 Oracle.  All Rights Reserved.
+ *
+ * Author: Darrick J. Wong <darrick.wong@xxxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it would be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write the Free Software Foundation,
+ * Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#ifndef XFS_SCRUB_UNICRASH_H_
+#define XFS_SCRUB_UNICRASH_H_
+
+/* Unicode name collision detection. */
+#ifdef HAVE_LIBUNISTRING
+bool unicrash_scan_dir(struct scrub_ctx *ctx, const char *descr, int fd,
+		off_t size);
+#else
+# define unicrash_scan_dir(c, d, f, s)		(true)
+#endif
+
+#if defined(HAVE_LIBUNISTRING) && defined(HAVE_ATTR_ROOT)
+bool unicrash_scan_fh_xattrs(struct scrub_ctx *ctx, const char *descr,
+		struct xfs_handle *handle, struct xfs_bstat *bstat);
+#else
+# define unicrash_scan_fh_xattrs(c, d, h, b)	(true)
+#endif
+
+#endif /* XFS_SCRUB_UNICRASH_H_ */

--
To unsubscribe from this list: send the line "unsubscribe linux-xfs" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [XFS Filesystem Development (older mail)]     [Linux Filesystem Development]     [Linux Audio Users]     [Yosemite Trails]     [Linux Kernel]     [Linux RAID]     [Linux SCSI]


  Powered by Linux