Blame SOURCES/0014-Fix-sbd-common-avoid-statting-potential-links.patch

dbaca1
From 5b4c866f7c0b4ef8061e131a1ee0d1c608d35054 Mon Sep 17 00:00:00 2001
dbaca1
From: Klaus Wenninger <klaus.wenninger@aon.at>
dbaca1
Date: Wed, 19 Sep 2018 16:15:27 +0200
dbaca1
Subject: [PATCH] Fix: sbd-common: avoid statting potential links
dbaca1
dbaca1
These potential links might be anything and statting - if just
dbaca1
allowed to stat chr-nodes (e.g. SELinux) - them would lead
dbaca1
to avc-logs in the SELinux case.
dbaca1
---
dbaca1
 src/sbd-common.c | 133 +++++++++++++++++++++++++++++++++++++++----------------
dbaca1
 1 file changed, 96 insertions(+), 37 deletions(-)
dbaca1
dbaca1
diff --git a/src/sbd-common.c b/src/sbd-common.c
dbaca1
index 679f946..cc84cd0 100644
dbaca1
--- a/src/sbd-common.c
dbaca1
+++ b/src/sbd-common.c
dbaca1
@@ -262,6 +262,12 @@ struct watchdog_list_item {
dbaca1
 	struct watchdog_list_item *next;
dbaca1
 };
dbaca1
 
dbaca1
+struct link_list_item {
dbaca1
+	char *dev_node;
dbaca1
+	char *link_name;
dbaca1
+	struct link_list_item *next;
dbaca1
+};
dbaca1
+
dbaca1
 static struct watchdog_list_item *watchdog_list = NULL;
dbaca1
 static int watchdog_list_items = 0;
dbaca1
 
dbaca1
@@ -275,6 +281,7 @@ watchdog_populate_list(void)
dbaca1
 	char entry_name[280];
dbaca1
 	DIR *dp;
dbaca1
 	char buf[280] = "";
dbaca1
+	struct link_list_item *link_list = NULL;
dbaca1
 
dbaca1
 	if (watchdog_list != NULL) {
dbaca1
 		return;
dbaca1
@@ -288,7 +295,7 @@ watchdog_populate_list(void)
dbaca1
 				FILE *file;
dbaca1
 
dbaca1
 				snprintf(entry_name, sizeof(entry_name),
dbaca1
-						SYS_CLASS_WATCHDOG "/%s/dev", entry->d_name);
dbaca1
+				         SYS_CLASS_WATCHDOG "/%s/dev", entry->d_name);
dbaca1
 				file = fopen(entry_name, "r");
dbaca1
 				if (file) {
dbaca1
 					int major, minor;
dbaca1
@@ -309,43 +316,59 @@ watchdog_populate_list(void)
dbaca1
 	/* search for watchdog nodes in /dev */
dbaca1
 	dp = opendir(WATCHDOG_NODEDIR);
dbaca1
 	if (dp) {
dbaca1
+		/* first go for links and memorize them */
dbaca1
 		while ((entry = readdir(dp))) {
dbaca1
-			if ((entry->d_type == DT_CHR) || (entry->d_type == DT_LNK)) {
dbaca1
-				struct stat statbuf;
dbaca1
+			if (entry->d_type == DT_LNK) {
dbaca1
+				int len;
dbaca1
 
dbaca1
 				snprintf(entry_name, sizeof(entry_name),
dbaca1
-						WATCHDOG_NODEDIR "%s", entry->d_name);
dbaca1
-				if (entry->d_type == DT_LNK) {
dbaca1
-					int len;
dbaca1
-
dbaca1
-					/* !realpath(entry_name, buf) unfortunately does a stat on
dbaca1
-					 * target so we can't really use it to check if links stay
dbaca1
-					 * within /dev without triggering e.g. AVC-logs (with
dbaca1
-					 * SELinux policy that just allows stat within /dev).
dbaca1
-					 * Without canonicalization that doesn't actually touch the
dbaca1
-					 * filesystem easily available introduce some limitations
dbaca1
-					 * for simplicity:
dbaca1
-					 * - just simple path without '..'
dbaca1
-					 * - just one level of symlinks (avoid e.g. loop-checking)
dbaca1
-					 */
dbaca1
-					len = readlink(entry_name, buf, sizeof(buf) - 1);
dbaca1
-					if ((len < 1) ||
dbaca1
-					    (len > sizeof(buf) - WATCHDOG_NODEDIR_LEN - 1)) {
dbaca1
-						continue;
dbaca1
-					}
dbaca1
-					buf[len] = '\0';
dbaca1
-					if (buf[0] != '/') {
dbaca1
-						memmove(&buf[WATCHDOG_NODEDIR_LEN], buf, len+1);
dbaca1
-						memcpy(buf, WATCHDOG_NODEDIR, WATCHDOG_NODEDIR_LEN);
dbaca1
-						len += WATCHDOG_NODEDIR_LEN;
dbaca1
-					}
dbaca1
-					if (strstr(buf, "/../") ||
dbaca1
-					    strncmp(WATCHDOG_NODEDIR, buf, WATCHDOG_NODEDIR_LEN) ||
dbaca1
-					    lstat(buf, &statbuf) ||
dbaca1
-					    !S_ISCHR(statbuf.st_mode)) {
dbaca1
-						continue;
dbaca1
-					}
dbaca1
+				         WATCHDOG_NODEDIR "%s", entry->d_name);
dbaca1
+
dbaca1
+				/* !realpath(entry_name, buf) unfortunately does a stat on
dbaca1
+				 * target so we can't really use it to check if links stay
dbaca1
+				 * within /dev without triggering e.g. AVC-logs (with
dbaca1
+				 * SELinux policy that just allows stat within /dev).
dbaca1
+				 * Without canonicalization that doesn't actually touch the
dbaca1
+				 * filesystem easily available introduce some limitations
dbaca1
+				 * for simplicity:
dbaca1
+				 * - just simple path without '..'
dbaca1
+				 * - just one level of symlinks (avoid e.g. loop-checking)
dbaca1
+				 */
dbaca1
+				len = readlink(entry_name, buf, sizeof(buf) - 1);
dbaca1
+				if ((len < 1) ||
dbaca1
+				    (len > sizeof(buf) - WATCHDOG_NODEDIR_LEN - 1)) {
dbaca1
+					continue;
dbaca1
+				}
dbaca1
+				buf[len] = '\0';
dbaca1
+				if (buf[0] != '/') {
dbaca1
+					memmove(&buf[WATCHDOG_NODEDIR_LEN], buf, len+1);
dbaca1
+					memcpy(buf, WATCHDOG_NODEDIR, WATCHDOG_NODEDIR_LEN);
dbaca1
+					len += WATCHDOG_NODEDIR_LEN;
dbaca1
+				}
dbaca1
+				if (strstr(buf, "/../") ||
dbaca1
+				    strncmp(WATCHDOG_NODEDIR, buf, WATCHDOG_NODEDIR_LEN)) {
dbaca1
+					continue;
dbaca1
+				} else {
dbaca1
+					/* just memorize to avoid statting the target - SELinux */
dbaca1
+					struct link_list_item *lli =
dbaca1
+						calloc(1, sizeof(struct link_list_item));
dbaca1
+
dbaca1
+					lli->dev_node = strdup(buf);
dbaca1
+					lli->link_name = strdup(entry_name);
dbaca1
+					lli->next = link_list;
dbaca1
+					link_list = lli;
dbaca1
 				}
dbaca1
+			}
dbaca1
+		}
dbaca1
+
dbaca1
+		rewinddir(dp);
dbaca1
+
dbaca1
+		while ((entry = readdir(dp))) {
dbaca1
+			if (entry->d_type == DT_CHR) {
dbaca1
+				struct stat statbuf;
dbaca1
+
dbaca1
+				snprintf(entry_name, sizeof(entry_name),
dbaca1
+				         WATCHDOG_NODEDIR "%s", entry->d_name);
dbaca1
 				if(!stat(entry_name, &statbuf) && S_ISCHR(statbuf.st_mode)) {
dbaca1
 					int i;
dbaca1
 
dbaca1
@@ -353,8 +376,9 @@ watchdog_populate_list(void)
dbaca1
 						if (statbuf.st_rdev == watchdogs[i]) {
dbaca1
 							int wdfd = watchdog_init_fd(entry_name, -1);
dbaca1
 							struct watchdog_list_item *wdg =
dbaca1
-									calloc(1, sizeof(struct watchdog_list_item));
dbaca1
+								calloc(1, sizeof(struct watchdog_list_item));
dbaca1
 							int len;
dbaca1
+							struct link_list_item *tmp_list = NULL;
dbaca1
 
dbaca1
 							wdg->dev = watchdogs[i];
dbaca1
 							wdg->dev_node = strdup(entry_name);
dbaca1
@@ -374,8 +398,8 @@ watchdog_populate_list(void)
dbaca1
 							}
dbaca1
 
dbaca1
 							snprintf(entry_name, sizeof(entry_name),
dbaca1
-								SYS_CHAR_DEV_DIR "/%d:%d/device/driver",
dbaca1
-								major(watchdogs[i]), minor(watchdogs[i]));
dbaca1
+							         SYS_CHAR_DEV_DIR "/%d:%d/device/driver",
dbaca1
+							         major(watchdogs[i]), minor(watchdogs[i]));
dbaca1
 							len = readlink(entry_name, buf, sizeof(buf) - 1);
dbaca1
 							if (len > 0) {
dbaca1
 								buf[len] = '\0';
dbaca1
@@ -385,14 +409,49 @@ watchdog_populate_list(void)
dbaca1
 							                   "Software Watchdog") == 0)) {
dbaca1
 								wdg->dev_driver = strdup("softdog");
dbaca1
 							}
dbaca1
+
dbaca1
+							/* create dupes if we have memorized links
dbaca1
+							 * to this node
dbaca1
+							 */
dbaca1
+							for (tmp_list = link_list; tmp_list;
dbaca1
+							     tmp_list = tmp_list->next) {
dbaca1
+								if (!strcmp(tmp_list->dev_node,
dbaca1
+								            wdg->dev_node)) {
dbaca1
+									struct watchdog_list_item *dupe_wdg =
dbaca1
+										calloc(1, sizeof(struct watchdog_list_item));
dbaca1
+
dbaca1
+									/* as long as we never purge watchdog_list
dbaca1
+									 * there is no need to dupe strings
dbaca1
+									 */
dbaca1
+									*dupe_wdg = *wdg;
dbaca1
+									dupe_wdg->dev_node = strdup(tmp_list->link_name);
dbaca1
+									dupe_wdg->next = watchdog_list;
dbaca1
+									watchdog_list = dupe_wdg;
dbaca1
+									watchdog_list_items++;
dbaca1
+								}
dbaca1
+								/* for performance reasons we could remove
dbaca1
+								 * the link_list entry
dbaca1
+								 */
dbaca1
+							}
dbaca1
 							break;
dbaca1
 						}
dbaca1
 					}
dbaca1
 				}
dbaca1
 			}
dbaca1
 		}
dbaca1
+
dbaca1
 		closedir(dp);
dbaca1
 	}
dbaca1
+
dbaca1
+	/* cleanup link list */
dbaca1
+	while (link_list) {
dbaca1
+		struct link_list_item *tmp_list = link_list;
dbaca1
+
dbaca1
+		link_list = link_list->next;
dbaca1
+		free(tmp_list->dev_node);
dbaca1
+		free(tmp_list->link_name);
dbaca1
+		free(tmp_list);
dbaca1
+	}
dbaca1
 }
dbaca1
 
dbaca1
 int watchdog_info(void)
dbaca1
-- 
dbaca1
1.8.3.1
dbaca1