diff mbox

[1/3] libselinux: Evaluate inodes in selinux_restorecon(3)

Message ID 1462893734-9509-1-git-send-email-richard_c_haines@btinternet.com (mailing list archive)
State Not Applicable
Headers show

Commit Message

Richard Haines May 10, 2016, 3:22 p.m. UTC
This patch transfers matchpathcon.c inode evaluation services to
selinux_restorecon.c and modifies them to also support setfiles(8)
inode services.

The overall objective is to modify restorecon(8) and setfiles(8)
to use selinux_restorecon(3) services and then, when ready
remove the deprecated matchpathcon services from libselinux.

Signed-off-by: Richard Haines <richard_c_haines@btinternet.com>
---
 libselinux/include/selinux/restorecon.h  |   4 +
 libselinux/man/man3/selinux_restorecon.3 |   5 +-
 libselinux/src/matchpathcon.c            | 139 +------------
 libselinux/src/selinux_restorecon.c      | 333 ++++++++++++++++++++++++++++---
 libselinux/utils/selinux_restorecon.c    |  14 +-
 5 files changed, 330 insertions(+), 165 deletions(-)

Comments

Stephen Smalley May 20, 2016, 4:26 p.m. UTC | #1
On 05/10/2016 11:22 AM, Richard Haines wrote:
> This patch transfers matchpathcon.c inode evaluation services to
> selinux_restorecon.c and modifies them to also support setfiles(8)
> inode services.
> 
> The overall objective is to modify restorecon(8) and setfiles(8)
> to use selinux_restorecon(3) services and then, when ready
> remove the deprecated matchpathcon services from libselinux.
> 
> Signed-off-by: Richard Haines <richard_c_haines@btinternet.com>
> ---
>  libselinux/include/selinux/restorecon.h  |   4 +
>  libselinux/man/man3/selinux_restorecon.3 |   5 +-
>  libselinux/src/matchpathcon.c            | 139 +------------
>  libselinux/src/selinux_restorecon.c      | 333 ++++++++++++++++++++++++++++---
>  libselinux/utils/selinux_restorecon.c    |  14 +-
>  5 files changed, 330 insertions(+), 165 deletions(-)
> 
> diff --git a/libselinux/include/selinux/restorecon.h b/libselinux/include/selinux/restorecon.h
> index ba1232e..0b93b0c 100644
> --- a/libselinux/include/selinux/restorecon.h
> +++ b/libselinux/include/selinux/restorecon.h
> @@ -46,6 +46,10 @@ extern int selinux_restorecon(const char *pathname,
>  /* Prevent descending into directories that have a different
>   * device number than the pathname from which the descent began */
>  #define SELINUX_RESTORECON_XDEV				128
> +/* Attempt to add an association between an inode and a context.
> + * If there is a different context that matched the inode,
> + * then use the first context that matched. */
> +#define SELINUX_RESTORECON_ADD_ASSOC			256

IIRC, the (original) behavior in setfiles was to use the higher priority
entry, i.e. the last matching specification in file_contexts, in the
case of a conflict.  Not sure if that is still the case.

>  
>  /**
>   * selinux_restorecon_set_sehandle - Set the global fc handle.
> diff --git a/libselinux/man/man3/selinux_restorecon.3 b/libselinux/man/man3/selinux_restorecon.3
> index 0293c4d..bbb6721 100644
> --- a/libselinux/man/man3/selinux_restorecon.3
> +++ b/libselinux/man/man3/selinux_restorecon.3
> @@ -68,7 +68,6 @@ If set, reset the files label to match the default specfile context.
>  If not set only reset the files "type" component of the context to match the
>  default specfile context.
>  .br
> -
>  .sp
>  .B SELINUX_RESTORECON_RECURSE
>  change file and directory labels recursively (descend directories)
> @@ -103,6 +102,10 @@ prevent descending into directories that have a different device number than
>  the
>  .I pathname
>  entry from which the descent began.
> +.sp
> +.B SELINUX_RESTORECON_ADD_ASSOC
> +attempt to add an association between an inode and a context. If there is a
> +different context that matched the inode, then use the first context that matched.

Ditto.

>  .RE
>  .sp
>  The behavior regarding the checking and updating of the SHA1 digest described
> diff --git a/libselinux/src/matchpathcon.c b/libselinux/src/matchpathcon.c
> index 5b495a0..6020737 100644
> --- a/libselinux/src/matchpathcon.c
> +++ b/libselinux/src/matchpathcon.c
> @@ -12,7 +12,7 @@ static __thread struct selabel_handle *hnd;
>  /*
>   * An array for mapping integers to contexts
>   */
> -static __thread char **con_array;
> +__thread char **con_array;

We should keep it static and provide helpers to access it if necessary
from other files.  But see below.

>  static __thread int con_array_size;
>  static __thread int con_array_used;
>  
> @@ -131,27 +131,11 @@ void set_matchpathcon_flags(unsigned int flags)
>  	notrans = flags & MATCHPATHCON_NOTRANS;
>  }
>  
> -/*
> - * An association between an inode and a 
> - * specification.  
> - */
> -typedef struct file_spec {
> -	ino_t ino;		/* inode number */
> -	int specind;		/* index of specification in spec */
> -	char *file;		/* full pathname for diagnostic messages about conflicts */
> -	struct file_spec *next;	/* next association in hash bucket chain */
> -} file_spec_t;
> -
> -/*
> - * The hash table of associations, hashed by inode number.
> - * Chaining is used for collisions, with elements ordered
> - * by inode number in each bucket.  Each hash bucket has a dummy 
> - * header.
> - */
> -#define HASH_BITS 16
> -#define HASH_BUCKETS (1 << HASH_BITS)
> -#define HASH_MASK (HASH_BUCKETS-1)
> -static file_spec_t *fl_head;
> +/* Ensure add_assoc and verbose are false when calling from matchpathcon */
> +extern int restorecon_filespec_add1(ino_t ino, int specind, const char *con,
> +			    const char *file, bool add_assoc, bool verbose);
> +extern void restorecon_filespec_eval(bool add_assoc, bool verbose);
> +extern void restorecon_filespec_destroy(bool add_assoc);

These should go in a private header, e.g. restorecon_internal.h, that is
included by both restorecon.c and this file.  But see below.

>
> diff --git a/libselinux/src/selinux_restorecon.c b/libselinux/src/selinux_restorecon.c
> index 17ed6fe..2794659 100644
> --- a/libselinux/src/selinux_restorecon.c
> +++ b/libselinux/src/selinux_restorecon.c
> @@ -42,6 +42,19 @@ static const char **fc_exclude_list = NULL;
>  static size_t fc_count = 0;
>  #define STAR_COUNT 1000
>  
> +/* restorecon_flags for passing to restorecon_sb() */
> +struct rest_flags {
> +	bool nochange;
> +	bool verbose;
> +	bool progress;
> +	bool specctx;
> +	bool add_assoc;
> +	bool ignore;
> +	bool recurse;
> +	bool userealpath;
> +	bool xdev;
> +};
> +
>  static void restorecon_init(void)
>  {
>  	struct selabel_handle *sehandle = NULL;
> @@ -66,6 +79,239 @@ static int check_excluded(const char *file)
>  	return 0;
>  }
>  
> +/*
> + * Support filespec services for selinux_restorecon(3) and matchpathcon(3).
> + * The matchpathcon services are deprecated and at some stage will be removed,
> + * the matchpathcon specific code here can then also be removed.
> + *
> + * selinux_restorecon(3) uses filespec services when the
> + * SELINUX_RESTORECON_ADD_ASSOC flag is set for adding associations between
> + * an inode and a context.
> + */
> +
> +/* Support for matchpathcon myprint() */
> +extern int myprintf_compat;
> +extern void __attribute__ ((format(printf, 1, 2)))
> +(*myprintf) (const char *fmt, ...);
> +#define COMPAT_LOG(type, fmt...) if (myprintf_compat)	  \
> +		myprintf(fmt);				  \
> +	else						  \
> +		selinux_log(type, fmt);

I hate the idea of carrying compat goo into a new file and function.
Also, these are defined in label_internal.h so you can include that
rather than redefining them here.

> +
> +/* Reference the con_array specified in matchpathcon.c */
> +extern __thread char **con_array;
> +
> +int restorecon_filespec_add(ino_t ino, const char *con,
> +			    const char *file, bool add_assoc, bool verbose);
> +int restorecon_filespec_add1(ino_t ino, int specind, const char *con,
> +			    const char *file, bool add_assoc, bool verbose);
> +void restorecon_filespec_eval(bool add_assoc, bool verbose);
> +void restorecon_filespec_destroy(bool add_assoc);

Belong in an internal header.

> +
> +/*
> + * Hold an association between an inode and a context or specification.
> + */
> +typedef struct file_spec {
> +	ino_t ino;	/* inode number */
> +	int specind;	/* index of specification in spec (matchpathcon) */
> +	char *con;	/* matched context (selinux_restorecon)*/
> +	char *file;	/* full pathname */
> +	struct file_spec *next;	/* next association in hash bucket chain */
> +} file_spec_t;

I'm trying to remember the history of filespec_add and friends.
First they existed only in setfiles.  Then I ported them to libselinux
as matchpathcon_filespec_*() and rewrote setfiles to use them.  Then a
revised implementation was added back to setfiles as part of the
conversion to selabel_open() and friends, and the matchpathcon versions
were just left for ABI compatibility but unused.  So I am unsure about
moving these over to restorecon.

> +
> +/*
> + * The hash table of associations, hashed by inode number.
> + * Chaining is used for collisions, with elements ordered
> + * by inode number in each bucket.  Each hash bucket has
> + * a dummy header.
> + */
> +#define HASH_BITS 16
> +#define HASH_BUCKETS (1 << HASH_BITS)
> +#define HASH_MASK (HASH_BUCKETS-1)
> +static file_spec_t *fl_head;
> +
> +/*
> + * Try to add an association between an inode and a context.
> + * If there is a different context that matched the inode,
> + * then use the first context that matched.
> + */
> +int hidden restorecon_filespec_add(ino_t ino, const char *con,
> +			    const char *file, bool add_assoc, bool verbose)
> +{
> +	return restorecon_filespec_add1(ino, -1, con, file, add_assoc, verbose);

What does -1 mean?

> +}
> +
> +int hidden restorecon_filespec_add1(ino_t ino, int specind,
> +				    const char *con,
> +				    const char *file, bool add_assoc,
> +				    bool verbose __attribute__((unused)))
> +{
> +	file_spec_t *prevfl, *fl;
> +	int h, ret;
> +	struct stat64 sb;
> +
> +	if (!fl_head) {
> +		fl_head = malloc(sizeof(file_spec_t) * HASH_BUCKETS);
> +		if (!fl_head)
> +			goto oom;
> +		memset(fl_head, 0, sizeof(file_spec_t) * HASH_BUCKETS);
> +	}
> +
> +	h = (ino + (ino >> HASH_BITS)) & HASH_MASK;
> +	for (prevfl = &fl_head[h], fl = fl_head[h].next; fl;
> +	     prevfl = fl, fl = fl->next) {
> +		if (ino == fl->ino) {
> +			ret = lstat64(fl->file, &sb);
> +			if (ret < 0 || sb.st_ino != ino) {
> +				if (add_assoc)
> +					free(fl->con);
> +				free(fl->file);
> +				fl->file = strdup(file);
> +				if (!fl->file)
> +					goto oom;
> +
> +				if (add_assoc) {
> +					fl->con = strdup(con);
> +					if (!fl->con)
> +						goto oom;
> +					return 1;
> +				} else {
> +					return fl->specind;
> +				}
> +			}
> +
> +			if (add_assoc) {
> +				if (strcmp(fl->con, con) == 0)
> +					return 1;
> +
> +				selinux_log(SELINUX_ERROR,
> +					"%s:  conflicting specifications for %s and %s, using %s.\n",
> +					__func__, file, fl->file, fl->con);
> +				free(fl->file);
> +				fl->file = strdup(file);
> +				if (!fl->file)
> +					goto oom;
> +				return 1;
> +			} else {
> +				if (!strcmp(con_array[fl->specind],
> +					    con_array[specind]))

What happens when specind was passed as -1 above?

Maybe we ought to just leave the matchpathcon ones alone (aside from
deprecating and ultimately removing them), and bring over the versions
added to setfiles when it was converted to using selabel_open().
Christopher J. PeBenito May 20, 2016, 5:04 p.m. UTC | #2
On 5/20/2016 12:26 PM, Stephen Smalley wrote:
> On 05/10/2016 11:22 AM, Richard Haines wrote:
>> This patch transfers matchpathcon.c inode evaluation services to
>> selinux_restorecon.c and modifies them to also support setfiles(8)
>> inode services.
>>
>> The overall objective is to modify restorecon(8) and setfiles(8)
>> to use selinux_restorecon(3) services and then, when ready
>> remove the deprecated matchpathcon services from libselinux.
>>
>> Signed-off-by: Richard Haines <richard_c_haines@btinternet.com>
>> ---
>>  libselinux/include/selinux/restorecon.h  |   4 +
>>  libselinux/man/man3/selinux_restorecon.3 |   5 +-
>>  libselinux/src/matchpathcon.c            | 139 +------------
>>  libselinux/src/selinux_restorecon.c      | 333 ++++++++++++++++++++++++++++---
>>  libselinux/utils/selinux_restorecon.c    |  14 +-
>>  5 files changed, 330 insertions(+), 165 deletions(-)
>>
>> diff --git a/libselinux/include/selinux/restorecon.h b/libselinux/include/selinux/restorecon.h
>> index ba1232e..0b93b0c 100644
>> --- a/libselinux/include/selinux/restorecon.h
>> +++ b/libselinux/include/selinux/restorecon.h
>> @@ -46,6 +46,10 @@ extern int selinux_restorecon(const char *pathname,
>>  /* Prevent descending into directories that have a different
>>   * device number than the pathname from which the descent began */
>>  #define SELINUX_RESTORECON_XDEV				128
>> +/* Attempt to add an association between an inode and a context.
>> + * If there is a different context that matched the inode,
>> + * then use the first context that matched. */
>> +#define SELINUX_RESTORECON_ADD_ASSOC			256
> 
> IIRC, the (original) behavior in setfiles was to use the higher priority
> entry, i.e. the last matching specification in file_contexts, in the
> case of a conflict.  Not sure if that is still the case.

Latter entries being higher priority would be my expectation in
refpolicy.  That's why we sort entries in order of specificity (least
specific at the top of the file to most specific at the end).
Richard Haines May 31, 2016, 1:05 p.m. UTC | #3
> On Friday, 20 May 2016, 17:24, Stephen Smalley <sds@tycho.nsa.gov> wrote:
> > On 05/10/2016 11:22 AM, Richard Haines wrote:
>>  This patch transfers matchpathcon.c inode evaluation services to
>>  selinux_restorecon.c and modifies them to also support setfiles(8)
>>  inode services.
>> 
>>  The overall objective is to modify restorecon(8) and setfiles(8)
>>  to use selinux_restorecon(3) services and then, when ready
>>  remove the deprecated matchpathcon services from libselinux.
>> 
>>  Signed-off-by: Richard Haines <richard_c_haines@btinternet.com>
>>  ---
>>   libselinux/include/selinux/restorecon.h  |   4 +
>>   libselinux/man/man3/selinux_restorecon.3 |   5 +-
>>   libselinux/src/matchpathcon.c            | 139 +------------
>>   libselinux/src/selinux_restorecon.c      | 333 
> ++++++++++++++++++++++++++++---
>>   libselinux/utils/selinux_restorecon.c    |  14 +-
>>   5 files changed, 330 insertions(+), 165 deletions(-)

>> 

----- snip ------
> 
> Maybe we ought to just leave the matchpathcon ones alone (aside from
> deprecating and ultimately removing them), and bring over the versions
> added to setfiles when it was converted to using selabel_open().

> 

I'll leave matchpathcon alone and port over the setfiles code.
diff mbox

Patch

diff --git a/libselinux/include/selinux/restorecon.h b/libselinux/include/selinux/restorecon.h
index ba1232e..0b93b0c 100644
--- a/libselinux/include/selinux/restorecon.h
+++ b/libselinux/include/selinux/restorecon.h
@@ -46,6 +46,10 @@  extern int selinux_restorecon(const char *pathname,
 /* Prevent descending into directories that have a different
  * device number than the pathname from which the descent began */
 #define SELINUX_RESTORECON_XDEV				128
+/* Attempt to add an association between an inode and a context.
+ * If there is a different context that matched the inode,
+ * then use the first context that matched. */
+#define SELINUX_RESTORECON_ADD_ASSOC			256
 
 /**
  * selinux_restorecon_set_sehandle - Set the global fc handle.
diff --git a/libselinux/man/man3/selinux_restorecon.3 b/libselinux/man/man3/selinux_restorecon.3
index 0293c4d..bbb6721 100644
--- a/libselinux/man/man3/selinux_restorecon.3
+++ b/libselinux/man/man3/selinux_restorecon.3
@@ -68,7 +68,6 @@  If set, reset the files label to match the default specfile context.
 If not set only reset the files "type" component of the context to match the
 default specfile context.
 .br
-
 .sp
 .B SELINUX_RESTORECON_RECURSE
 change file and directory labels recursively (descend directories)
@@ -103,6 +102,10 @@  prevent descending into directories that have a different device number than
 the
 .I pathname
 entry from which the descent began.
+.sp
+.B SELINUX_RESTORECON_ADD_ASSOC
+attempt to add an association between an inode and a context. If there is a
+different context that matched the inode, then use the first context that matched.
 .RE
 .sp
 The behavior regarding the checking and updating of the SHA1 digest described
diff --git a/libselinux/src/matchpathcon.c b/libselinux/src/matchpathcon.c
index 5b495a0..6020737 100644
--- a/libselinux/src/matchpathcon.c
+++ b/libselinux/src/matchpathcon.c
@@ -12,7 +12,7 @@  static __thread struct selabel_handle *hnd;
 /*
  * An array for mapping integers to contexts
  */
-static __thread char **con_array;
+__thread char **con_array;
 static __thread int con_array_size;
 static __thread int con_array_used;
 
@@ -131,27 +131,11 @@  void set_matchpathcon_flags(unsigned int flags)
 	notrans = flags & MATCHPATHCON_NOTRANS;
 }
 
-/*
- * An association between an inode and a 
- * specification.  
- */
-typedef struct file_spec {
-	ino_t ino;		/* inode number */
-	int specind;		/* index of specification in spec */
-	char *file;		/* full pathname for diagnostic messages about conflicts */
-	struct file_spec *next;	/* next association in hash bucket chain */
-} file_spec_t;
-
-/*
- * The hash table of associations, hashed by inode number.
- * Chaining is used for collisions, with elements ordered
- * by inode number in each bucket.  Each hash bucket has a dummy 
- * header.
- */
-#define HASH_BITS 16
-#define HASH_BUCKETS (1 << HASH_BITS)
-#define HASH_MASK (HASH_BUCKETS-1)
-static file_spec_t *fl_head;
+/* Ensure add_assoc and verbose are false when calling from matchpathcon */
+extern int restorecon_filespec_add1(ino_t ino, int specind, const char *con,
+			    const char *file, bool add_assoc, bool verbose);
+extern void restorecon_filespec_eval(bool add_assoc, bool verbose);
+extern void restorecon_filespec_destroy(bool add_assoc);
 
 /*
  * Try to add an association between an inode and
@@ -162,71 +146,7 @@  static file_spec_t *fl_head;
  */
 int matchpathcon_filespec_add(ino_t ino, int specind, const char *file)
 {
-	file_spec_t *prevfl, *fl;
-	int h, ret;
-	struct stat sb;
-
-	if (!fl_head) {
-		fl_head = malloc(sizeof(file_spec_t) * HASH_BUCKETS);
-		if (!fl_head)
-			goto oom;
-		memset(fl_head, 0, sizeof(file_spec_t) * HASH_BUCKETS);
-	}
-
-	h = (ino + (ino >> HASH_BITS)) & HASH_MASK;
-	for (prevfl = &fl_head[h], fl = fl_head[h].next; fl;
-	     prevfl = fl, fl = fl->next) {
-		if (ino == fl->ino) {
-			ret = lstat(fl->file, &sb);
-			if (ret < 0 || sb.st_ino != ino) {
-				fl->specind = specind;
-				free(fl->file);
-				fl->file = malloc(strlen(file) + 1);
-				if (!fl->file)
-					goto oom;
-				strcpy(fl->file, file);
-				return fl->specind;
-
-			}
-
-			if (!strcmp(con_array[fl->specind],
-				    con_array[specind]))
-				return fl->specind;
-
-			myprintf
-			    ("%s:  conflicting specifications for %s and %s, using %s.\n",
-			     __FUNCTION__, file, fl->file,
-			     con_array[fl->specind]);
-			free(fl->file);
-			fl->file = malloc(strlen(file) + 1);
-			if (!fl->file)
-				goto oom;
-			strcpy(fl->file, file);
-			return fl->specind;
-		}
-
-		if (ino > fl->ino)
-			break;
-	}
-
-	fl = malloc(sizeof(file_spec_t));
-	if (!fl)
-		goto oom;
-	fl->ino = ino;
-	fl->specind = specind;
-	fl->file = malloc(strlen(file) + 1);
-	if (!fl->file)
-		goto oom_freefl;
-	strcpy(fl->file, file);
-	fl->next = prevfl->next;
-	prevfl->next = fl;
-	return fl->specind;
-      oom_freefl:
-	free(fl);
-      oom:
-	myprintf("%s:  insufficient memory for file label entry for %s\n",
-		 __FUNCTION__, file);
-	return -1;
+	return restorecon_filespec_add1(ino, specind, NULL, file, 0, 0);
 }
 
 /*
@@ -234,30 +154,7 @@  int matchpathcon_filespec_add(ino_t ino, int specind, const char *file)
  */
 void matchpathcon_filespec_eval(void)
 {
-	file_spec_t *fl;
-	int h, used, nel, len, longest;
-
-	if (!fl_head)
-		return;
-
-	used = 0;
-	longest = 0;
-	nel = 0;
-	for (h = 0; h < HASH_BUCKETS; h++) {
-		len = 0;
-		for (fl = fl_head[h].next; fl; fl = fl->next) {
-			len++;
-		}
-		if (len)
-			used++;
-		if (len > longest)
-			longest = len;
-		nel += len;
-	}
-
-	myprintf
-	    ("%s:  hash table stats: %d elements, %d/%d buckets used, longest chain length %d\n",
-	     __FUNCTION__, nel, used, HASH_BUCKETS, longest);
+	return restorecon_filespec_eval(0, 0);
 }
 
 /*
@@ -265,26 +162,8 @@  void matchpathcon_filespec_eval(void)
  */
 void matchpathcon_filespec_destroy(void)
 {
-	file_spec_t *fl, *tmp;
-	int h;
-
 	free_array_elts();
-
-	if (!fl_head)
-		return;
-
-	for (h = 0; h < HASH_BUCKETS; h++) {
-		fl = fl_head[h].next;
-		while (fl) {
-			tmp = fl;
-			fl = fl->next;
-			free(tmp->file);
-			free(tmp);
-		}
-		fl_head[h].next = NULL;
-	}
-	free(fl_head);
-	fl_head = NULL;
+	restorecon_filespec_destroy(0);
 }
 
 static void matchpathcon_thread_destructor(void __attribute__((unused)) *ptr)
diff --git a/libselinux/src/selinux_restorecon.c b/libselinux/src/selinux_restorecon.c
index 17ed6fe..2794659 100644
--- a/libselinux/src/selinux_restorecon.c
+++ b/libselinux/src/selinux_restorecon.c
@@ -42,6 +42,19 @@  static const char **fc_exclude_list = NULL;
 static size_t fc_count = 0;
 #define STAR_COUNT 1000
 
+/* restorecon_flags for passing to restorecon_sb() */
+struct rest_flags {
+	bool nochange;
+	bool verbose;
+	bool progress;
+	bool specctx;
+	bool add_assoc;
+	bool ignore;
+	bool recurse;
+	bool userealpath;
+	bool xdev;
+};
+
 static void restorecon_init(void)
 {
 	struct selabel_handle *sehandle = NULL;
@@ -66,6 +79,239 @@  static int check_excluded(const char *file)
 	return 0;
 }
 
+/*
+ * Support filespec services for selinux_restorecon(3) and matchpathcon(3).
+ * The matchpathcon services are deprecated and at some stage will be removed,
+ * the matchpathcon specific code here can then also be removed.
+ *
+ * selinux_restorecon(3) uses filespec services when the
+ * SELINUX_RESTORECON_ADD_ASSOC flag is set for adding associations between
+ * an inode and a context.
+ */
+
+/* Support for matchpathcon myprint() */
+extern int myprintf_compat;
+extern void __attribute__ ((format(printf, 1, 2)))
+(*myprintf) (const char *fmt, ...);
+#define COMPAT_LOG(type, fmt...) if (myprintf_compat)	  \
+		myprintf(fmt);				  \
+	else						  \
+		selinux_log(type, fmt);
+
+/* Reference the con_array specified in matchpathcon.c */
+extern __thread char **con_array;
+
+int restorecon_filespec_add(ino_t ino, const char *con,
+			    const char *file, bool add_assoc, bool verbose);
+int restorecon_filespec_add1(ino_t ino, int specind, const char *con,
+			    const char *file, bool add_assoc, bool verbose);
+void restorecon_filespec_eval(bool add_assoc, bool verbose);
+void restorecon_filespec_destroy(bool add_assoc);
+
+/*
+ * Hold an association between an inode and a context or specification.
+ */
+typedef struct file_spec {
+	ino_t ino;	/* inode number */
+	int specind;	/* index of specification in spec (matchpathcon) */
+	char *con;	/* matched context (selinux_restorecon)*/
+	char *file;	/* full pathname */
+	struct file_spec *next;	/* next association in hash bucket chain */
+} file_spec_t;
+
+/*
+ * The hash table of associations, hashed by inode number.
+ * Chaining is used for collisions, with elements ordered
+ * by inode number in each bucket.  Each hash bucket has
+ * a dummy header.
+ */
+#define HASH_BITS 16
+#define HASH_BUCKETS (1 << HASH_BITS)
+#define HASH_MASK (HASH_BUCKETS-1)
+static file_spec_t *fl_head;
+
+/*
+ * Try to add an association between an inode and a context.
+ * If there is a different context that matched the inode,
+ * then use the first context that matched.
+ */
+int hidden restorecon_filespec_add(ino_t ino, const char *con,
+			    const char *file, bool add_assoc, bool verbose)
+{
+	return restorecon_filespec_add1(ino, -1, con, file, add_assoc, verbose);
+}
+
+int hidden restorecon_filespec_add1(ino_t ino, int specind,
+				    const char *con,
+				    const char *file, bool add_assoc,
+				    bool verbose __attribute__((unused)))
+{
+	file_spec_t *prevfl, *fl;
+	int h, ret;
+	struct stat64 sb;
+
+	if (!fl_head) {
+		fl_head = malloc(sizeof(file_spec_t) * HASH_BUCKETS);
+		if (!fl_head)
+			goto oom;
+		memset(fl_head, 0, sizeof(file_spec_t) * HASH_BUCKETS);
+	}
+
+	h = (ino + (ino >> HASH_BITS)) & HASH_MASK;
+	for (prevfl = &fl_head[h], fl = fl_head[h].next; fl;
+	     prevfl = fl, fl = fl->next) {
+		if (ino == fl->ino) {
+			ret = lstat64(fl->file, &sb);
+			if (ret < 0 || sb.st_ino != ino) {
+				if (add_assoc)
+					free(fl->con);
+				free(fl->file);
+				fl->file = strdup(file);
+				if (!fl->file)
+					goto oom;
+
+				if (add_assoc) {
+					fl->con = strdup(con);
+					if (!fl->con)
+						goto oom;
+					return 1;
+				} else {
+					return fl->specind;
+				}
+			}
+
+			if (add_assoc) {
+				if (strcmp(fl->con, con) == 0)
+					return 1;
+
+				selinux_log(SELINUX_ERROR,
+					"%s:  conflicting specifications for %s and %s, using %s.\n",
+					__func__, file, fl->file, fl->con);
+				free(fl->file);
+				fl->file = strdup(file);
+				if (!fl->file)
+					goto oom;
+				return 1;
+			} else {
+				if (!strcmp(con_array[fl->specind],
+					    con_array[specind]))
+					return fl->specind;
+
+				myprintf("matchpathcon_filespec_add:  conflicting specifications for %s and %s, using %s.\n",
+				     file, fl->file, con_array[fl->specind]);
+				free(fl->file);
+				fl->file = strdup(file);
+				if (!fl->file)
+					goto oom;
+				return fl->specind;
+			}
+		}
+
+		if (ino > fl->ino)
+			break;
+	}
+
+	fl = malloc(sizeof(file_spec_t));
+	if (!fl)
+		goto oom;
+	fl->ino = ino;
+
+	if (add_assoc) {
+		fl->con = strdup(con);
+		if (!fl->con)
+			goto oom_freefl;
+	} else {
+		fl->specind = specind;
+	}
+
+	fl->file = strdup(file);
+	if (!fl->file)
+		goto oom_freefl;
+	fl->next = prevfl->next;
+	prevfl->next = fl;
+
+	if (add_assoc)
+		return 0;
+	return fl->specind;
+
+
+oom_freefl:
+	free(fl);
+oom:
+	if (add_assoc)
+		selinux_log(SELINUX_ERROR,
+			"%s:  insufficient memory for file label entry for %s\n",
+			__func__, file);
+	else
+		myprintf("matchpathcon_filespec_add:  insufficient memory for file label entry for %s\n", file);
+
+	return -1;
+}
+
+/*
+ * Evaluate the association hash table distribution.
+ */
+void hidden restorecon_filespec_eval(bool add_assoc, bool verbose)
+{
+	file_spec_t *fl;
+	int h, used, nel, len, longest;
+
+	if (!fl_head)
+		return;
+
+	used = 0;
+	longest = 0;
+	nel = 0;
+	for (h = 0; h < HASH_BUCKETS; h++) {
+		len = 0;
+		for (fl = fl_head[h].next; fl; fl = fl->next)
+			len++;
+
+		if (len)
+			used++;
+		if (len > longest)
+			longest = len;
+		nel += len;
+	}
+
+	if (!add_assoc) {
+		myprintf("matchpathcon_filespec_eval:  hash table stats: %d elements, %d/%d buckets used, longest chain length %d\n",
+		    nel, used, HASH_BUCKETS, longest);
+	} else if (verbose) {
+		selinux_log(SELINUX_INFO,
+		    "%s:  hash table stats: %d elements, %d/%d buckets used, longest chain length %d\n",
+		     __func__, nel, used, HASH_BUCKETS, longest);
+	}
+}
+
+/*
+ * Destroy the association hash table.
+ */
+void hidden restorecon_filespec_destroy(bool add_assoc)
+{
+	file_spec_t *fl, *tmp;
+	int h;
+
+	if (!fl_head)
+		return;
+
+	for (h = 0; h < HASH_BUCKETS; h++) {
+		fl = fl_head[h].next;
+		while (fl) {
+			tmp = fl;
+			fl = fl->next;
+			if (add_assoc)
+				free(tmp->con);
+			free(tmp->file);
+			free(tmp);
+		}
+		fl_head[h].next = NULL;
+	}
+	free(fl_head);
+	fl_head = NULL;
+}
+/* End filespec services */
+
 /* Called if SELINUX_RESTORECON_SET_SPECFILE_CTX is not set to check if
  * the type components differ, updating newtypecon if so. */
 static int compare_types(char *curcon, char *newcon, char **newtypecon)
@@ -109,8 +355,7 @@  out:
 }
 
 static int restorecon_sb(const char *pathname, const struct stat *sb,
-					    bool nochange, bool verbose,
-					    bool progress, bool specctx)
+			    struct rest_flags *flags)
 {
 	char *newcon = NULL;
 	char *curcon = NULL;
@@ -121,6 +366,25 @@  static int restorecon_sb(const char *pathname, const struct stat *sb,
 	if (selabel_lookup_raw(fc_sehandle, &newcon, pathname, sb->st_mode) < 0)
 		return 0; /* no match, but not an error */
 
+	if (flags->add_assoc) {
+		rc = restorecon_filespec_add(sb->st_ino, newcon, pathname,
+					     flags->add_assoc, flags->verbose);
+
+		if (rc < 0) {
+			selinux_log(SELINUX_ERROR,
+				    "restorecon_filespec_add error: %s\n",
+				    pathname);
+			freecon(newcon);
+			return -1;
+		}
+
+		if (rc > 0) {
+			/* Already an association and it took precedence. */
+			freecon(newcon);
+			return 0;
+		}
+	}
+
 	if (lgetfilecon_raw(pathname, &curcon) < 0) {
 		if (errno != ENODATA)
 			goto err;
@@ -128,7 +392,7 @@  static int restorecon_sb(const char *pathname, const struct stat *sb,
 		curcon = NULL;
 	}
 
-	if (progress) {
+	if (flags->progress) {
 		fc_count++;
 		if (fc_count % STAR_COUNT == 0) {
 			fprintf(stdout, "*");
@@ -137,9 +401,9 @@  static int restorecon_sb(const char *pathname, const struct stat *sb,
 	}
 
 	if (strcmp(curcon, newcon) != 0) {
-		if (!specctx && curcon &&
+		if (!flags->specctx && curcon &&
 				    (is_context_customizable(curcon) > 0)) {
-			if (verbose) {
+			if (flags->verbose) {
 				selinux_log(SELINUX_INFO,
 				 "%s not reset as customized by admin to %s\n",
 							    pathname, curcon);
@@ -147,7 +411,7 @@  static int restorecon_sb(const char *pathname, const struct stat *sb,
 			}
 		}
 
-		if (!specctx && curcon) {
+		if (!flags->specctx && curcon) {
 			/* If types different then update newcon. */
 			rc = compare_types(curcon, newcon, &newtypecon);
 			if (rc)
@@ -161,13 +425,13 @@  static int restorecon_sb(const char *pathname, const struct stat *sb,
 			}
 		}
 
-		if (!nochange) {
+		if (!flags->nochange) {
 			if (lsetfilecon(pathname, newcon) < 0)
 				goto err;
 			updated = true;
 		}
 
-		if (verbose)
+		if (flags->verbose)
 			selinux_log(SELINUX_INFO,
 				    "%s %s from %s to %s\n",
 				    updated ? "Relabeled" : "Would relabel",
@@ -196,22 +460,27 @@  err:
 int selinux_restorecon(const char *pathname_orig,
 				    unsigned int restorecon_flags)
 {
-	bool ignore = (restorecon_flags &
-		    SELINUX_RESTORECON_IGNORE_DIGEST) ? true : false;
-	bool nochange = (restorecon_flags &
+	struct rest_flags flags;
+
+	flags.nochange = (restorecon_flags &
 		    SELINUX_RESTORECON_NOCHANGE) ? true : false;
-	bool verbose = (restorecon_flags &
+	flags.verbose = (restorecon_flags &
 		    SELINUX_RESTORECON_VERBOSE) ? true : false;
-	bool progress = (restorecon_flags &
+	flags.progress = (restorecon_flags &
 		    SELINUX_RESTORECON_PROGRESS) ? true : false;
-	bool recurse = (restorecon_flags &
-		    SELINUX_RESTORECON_RECURSE) ? true : false;
-	bool specctx = (restorecon_flags &
+	flags.specctx = (restorecon_flags &
 		    SELINUX_RESTORECON_SET_SPECFILE_CTX) ? true : false;
-	bool userealpath = (restorecon_flags &
+	flags.add_assoc = (restorecon_flags &
+		   SELINUX_RESTORECON_ADD_ASSOC) ? true : false;
+	flags.ignore = (restorecon_flags &
+		    SELINUX_RESTORECON_IGNORE_DIGEST) ? true : false;
+	flags.recurse = (restorecon_flags &
+		    SELINUX_RESTORECON_RECURSE) ? true : false;
+	flags.userealpath = (restorecon_flags &
 		   SELINUX_RESTORECON_REALPATH) ? true : false;
-	bool xdev = (restorecon_flags &
+	flags.xdev = (restorecon_flags &
 		   SELINUX_RESTORECON_XDEV) ? true : false;
+
 	bool issys;
 	bool setrestoreconlast = true; /* TRUE = set xattr RESTORECON_LAST
 					* FALSE = don't use xattr */
@@ -226,8 +495,8 @@  int selinux_restorecon(const char *pathname_orig,
 	char *xattr_value = NULL;
 	ssize_t size;
 
-	if (verbose && progress)
-		verbose = false;
+	if (flags.verbose && flags.progress)
+		flags.verbose = false;
 
 	__selinux_once(fc_once, restorecon_init);
 
@@ -244,7 +513,7 @@  int selinux_restorecon(const char *pathname_orig,
 	 * Convert passed-in pathname to canonical pathname by resolving
 	 * realpath of containing dir, then appending last component name.
 	 */
-	if (userealpath) {
+	if (flags.userealpath) {
 		pathbname = basename((char *)pathname_orig);
 		if (!strcmp(pathbname, "/") || !strcmp(pathbname, ".") ||
 					    !strcmp(pathbname, "..")) {
@@ -284,9 +553,8 @@  int selinux_restorecon(const char *pathname_orig,
 	if ((sb.st_mode & S_IFDIR) != S_IFDIR)
 		setrestoreconlast = false;
 
-	if (!recurse) {
-		error = restorecon_sb(pathname, &sb, nochange, verbose,
-						    progress, specctx);
+	if (!flags.recurse) {
+		error = restorecon_sb(pathname, &sb, &flags);
 		goto cleanup;
 	}
 
@@ -304,7 +572,7 @@  int selinux_restorecon(const char *pathname_orig,
 		size = getxattr(pathname, RESTORECON_LAST, xattr_value,
 							    fc_digest_len);
 
-		if (!ignore && size == fc_digest_len &&
+		if (!flags.ignore && size == fc_digest_len &&
 			    memcmp(fc_digest, xattr_value, fc_digest_len)
 								    == 0) {
 			selinux_log(SELINUX_INFO,
@@ -315,7 +583,7 @@  int selinux_restorecon(const char *pathname_orig,
 		}
 	}
 
-	if (xdev)
+	if (flags.xdev)
 		fts_flags = FTS_PHYSICAL | FTS_NOCHDIR | FTS_XDEV;
 	else
 		fts_flags = FTS_PHYSICAL | FTS_NOCHDIR;
@@ -375,22 +643,27 @@  int selinux_restorecon(const char *pathname_orig,
 			}
 
 			error |= restorecon_sb(ftsent->fts_path,
-				    ftsent->fts_statp, nochange,
-				    verbose, progress, specctx);
+					       ftsent->fts_statp, &flags);
 			break;
 		}
 	}
 
 	/* Labeling successful. Mark the top level directory as completed. */
-	if (setrestoreconlast && !nochange && !error) {
+	if (setrestoreconlast && !flags.nochange && !error) {
 		error = setxattr(pathname, RESTORECON_LAST, fc_digest,
 						    fc_digest_len, 0);
-		if (!error && verbose)
+		if (!error && flags.verbose)
 			selinux_log(SELINUX_INFO,
 				   "Updated digest for: %s\n", pathname);
 	}
 
 out:
+	if (flags.add_assoc) {
+		if (flags.verbose)
+			restorecon_filespec_eval(flags.add_assoc,
+						    flags.verbose);
+		restorecon_filespec_destroy(flags.add_assoc);
+	}
 	sverrno = errno;
 	(void) fts_close(fts);
 	errno = sverrno;
diff --git a/libselinux/utils/selinux_restorecon.c b/libselinux/utils/selinux_restorecon.c
index 52352c5..2552d63 100644
--- a/libselinux/utils/selinux_restorecon.c
+++ b/libselinux/utils/selinux_restorecon.c
@@ -37,7 +37,7 @@  static int validate_context(char **contextp)
 static void usage(const char *progname)
 {
 	fprintf(stderr,
-		"\nusage: %s [-FCnRrdei] [-v|-P] [-p policy] [-f specfile] "
+		"\nusage: %s [-FCnRrdeia] [-v|-P] [-p policy] [-f specfile] "
 		"pathname ...\n"
 		"Where:\n\t"
 		"-F  Set the label to that in specfile.\n\t"
@@ -55,8 +55,11 @@  static void usage(const char *progname)
 		"different\n\t    device number than the pathname from  which "
 		"the descent began.\n\t"
 		"-e  Exclude this file/directory (add multiple -e entries).\n\t"
-		"-i  Do not set SELABEL_OPT_VALIDATE option in selabel_open(3)"
-		" then call\n\t    selinux_restorecon_set_sehandle(3).\n\t"
+		"-i  Do not set SELABEL_OPT_DIGEST option when calling "
+		" selabel_open(3).\n\t"
+		"-a  Add an association between an inode and a context.\n\t"
+		"    If there is a different context that matched the inode,\n\t"
+		"    then use the first context that matched.\n\t"
 		"-p  Optional binary policy file (also sets validate context "
 		"option).\n\t"
 		"-f  Optional file contexts file.\n\t"
@@ -115,7 +118,7 @@  int main(int argc, char **argv)
 	exclude_list = NULL;
 	exclude_count = 0;
 
-	while ((opt = getopt(argc, argv, "iFCnRvPrde:f:p:")) > 0) {
+	while ((opt = getopt(argc, argv, "iFCnRvPrdae:f:p:")) > 0) {
 		switch (opt) {
 		case 'F':
 			restorecon_flags |=
@@ -187,6 +190,9 @@  int main(int argc, char **argv)
 		case 'i':
 			ignore_digest = true;
 			break;
+		case 'a':
+			restorecon_flags |= SELINUX_RESTORECON_ADD_ASSOC;
+			break;
 		default:
 			usage(argv[0]);
 		}