diff --git a/libselinux/src/label_file.c b/libselinux/src/label_file.c
index b7ff8c09..be1ee11a 100644
--- a/libselinux/src/label_file.c
+++ b/libselinux/src/label_file.c
@@ -1188,27 +1188,28 @@ static int process_file(const char *path, const char *suffix,
 	return -1;
 }
 
-static void selabel_subs_fini(struct selabel_sub *subs, uint32_t num)
+static void selabel_subs_fini(struct selabel_sub *ptr)
 {
-	for (uint32_t i = 0; i < num; i++) {
-		free(subs[i].src);
-		free(subs[i].dst);
+	struct selabel_sub *next;
+
+	while (ptr) {
+		next = ptr->next;
+		free(ptr->src);
+		free(ptr->dst);
+		free(ptr);
+		ptr = next;
 	}
-
-	free(subs);
 }
 
-static char *selabel_apply_subs(const struct selabel_sub *subs, uint32_t num, const char *src)
+static char *selabel_sub(const struct selabel_sub *ptr, const char *src)
 {
-	char *dst;
-	uint32_t len;
-
-	for (uint32_t i = 0; i < num; i++) {
-		const struct selabel_sub *ptr = &subs[i];
+	char *dst = NULL;
+	unsigned int len;
 
+	while (ptr) {
 		if (strncmp(src, ptr->src, ptr->slen) == 0 ) {
 			if (src[ptr->slen] == '/' ||
-			    src[ptr->slen] == '\0') {
+			    src[ptr->slen] == 0) {
 				if ((src[ptr->slen] == '/') &&
 				    (strcmp(ptr->dst, "/") == 0))
 					len = ptr->slen + 1;
@@ -1219,38 +1220,34 @@ static char *selabel_apply_subs(const struct selabel_sub *subs, uint32_t num, co
 				return dst;
 			}
 		}
+		ptr = ptr->next;
 	}
-
 	return NULL;
 }
 
 #if !defined(BUILD_HOST) && !defined(ANDROID)
 static int selabel_subs_init(const char *path, struct selabel_digest *digest,
-			     struct selabel_sub **out_subs,
-			     uint32_t *out_num, uint32_t *out_alloc)
+			     struct selabel_sub **out_subs)
 {
 	char buf[1024];
-	FILE *cfg;
+	FILE *cfg = fopen(path, "re");
+	struct selabel_sub *list = NULL, *sub = NULL;
 	struct stat sb;
-	struct selabel_sub *tmp = NULL;
-	uint32_t tmp_num = 0, tmp_alloc = 0;
-	char *src_cpy = NULL, *dst_cpy = NULL;
-	int rc;
+	int status = -1;
 
 	*out_subs = NULL;
-	*out_num = 0;
-	*out_alloc = 0;
-
-	cfg = fopen(path, "re");
 	if (!cfg) {
 		/* If the file does not exist, it is not fatal */
 		return (errno == ENOENT) ? 0 : -1;
 	}
 
+	if (fstat(fileno(cfg), &sb) < 0)
+		goto out;
+
 	while (fgets_unlocked(buf, sizeof(buf) - 1, cfg)) {
-		char *ptr;
+		char *ptr = NULL;
 		char *src = buf;
-		char *dst;
+		char *dst = NULL;
 		size_t len;
 
 		while (*src && isspace((unsigned char)*src))
@@ -1278,68 +1275,62 @@ static int selabel_subs_init(const char *path, struct selabel_digest *digest,
 			goto err;
 		}
 
-		src_cpy = strdup(src);
-		if (!src_cpy)
+		sub = calloc(1, sizeof(*sub));
+		if (! sub)
 			goto err;
 
-		dst_cpy = strdup(dst);
-		if (!dst_cpy)
+		sub->src = strdup(src);
+		if (! sub->src)
 			goto err;
 
-		rc = GROW_ARRAY(tmp);
-		if (rc)
+		sub->dst = strdup(dst);
+		if (! sub->dst)
 			goto err;
 
-		tmp[tmp_num++] = (struct selabel_sub) {
-			.src = src_cpy,
-			.slen = len,
-			.dst = dst_cpy,
-		};
-		src_cpy = NULL;
-		dst_cpy = NULL;
+		sub->slen = len;
+		sub->next = list;
+		list = sub;
+		sub = NULL;
 	}
 
-	rc = fstat(fileno(cfg), &sb);
-	if (rc < 0)
-		goto err;
-
 	if (digest_add_specfile(digest, cfg, NULL, sb.st_size, path) < 0)
 		goto err;
 
-	*out_subs = tmp;
-	*out_num = tmp_num;
-	*out_alloc = tmp_alloc;
+	*out_subs = list;
+	status = 0;
 
+out:
 	fclose(cfg);
-
-	return 0;
-
+	return status;
 err:
-	free(dst_cpy);
-	free(src_cpy);
-	for (uint32_t i = 0; i < tmp_num; i++) {
-		free(tmp[i].src);
-		free(tmp[i].dst);
+	if (sub)
+		free(sub->src);
+	free(sub);
+	while (list) {
+		sub = list->next;
+		free(list->src);
+		free(list->dst);
+		free(list);
+		list = sub;
 	}
-	free(tmp);
-	fclose_errno_safe(cfg);
-	return -1;
+	goto out;
 }
 #endif
 
 static char *selabel_sub_key(const struct saved_data *data, const char *key)
 {
-	char *ptr, *dptr;
+	char *ptr = NULL;
+	char *dptr = NULL;
 
-	ptr = selabel_apply_subs(data->subs, data->subs_num, key);
+	ptr = selabel_sub(data->subs, key);
 	if (ptr) {
-		dptr = selabel_apply_subs(data->dist_subs, data->dist_subs_num, ptr);
+		dptr = selabel_sub(data->dist_subs, ptr);
 		if (dptr) {
 			free(ptr);
 			ptr = dptr;
 		}
 	} else {
-		ptr = selabel_apply_subs(data->dist_subs, data->dist_subs_num, key);
+		ptr = selabel_sub(data->dist_subs, key);
 	}
 
 	return ptr;
@@ -1384,25 +1375,23 @@ static int init(struct selabel_handle *rec, const struct selinux_opt *opts,
 	if (!path) {
 		status = selabel_subs_init(
 			selinux_file_context_subs_dist_path(),
-			rec->digest,
-			&data->dist_subs, &data->dist_subs_num, &data->dist_subs_alloc);
+			rec->digest, &data->dist_subs);
 		if (status)
 			goto finish;
 		status = selabel_subs_init(selinux_file_context_subs_path(),
-			rec->digest,
-			&data->subs, &data->subs_num, &data->subs_alloc);
+			rec->digest, &data->subs);
 		if (status)
 			goto finish;
 		path = selinux_file_context_path();
 	} else {
 		snprintf(subs_file, sizeof(subs_file), "%s.subs_dist", path);
 		status = selabel_subs_init(subs_file, rec->digest,
-					   &data->dist_subs, &data->dist_subs_num, &data->dist_subs_alloc);
+					   &data->dist_subs);
 		if (status)
 			goto finish;
 		snprintf(subs_file, sizeof(subs_file), "%s.subs", path);
 		status = selabel_subs_init(subs_file, rec->digest,
-					   &data->subs, &data->subs_num, &data->subs_alloc);
+					   &data->subs);
 		if (status)
 			goto finish;
 	}
@@ -1470,8 +1459,8 @@ static void closef(struct selabel_handle *rec)
 	if (!data)
 		return;
 
-	selabel_subs_fini(data->subs, data->subs_num);
-	selabel_subs_fini(data->dist_subs, data->dist_subs_num);
+	selabel_subs_fini(data->subs);
+	selabel_subs_fini(data->dist_subs);
 
 	free_spec_node(data->root);
 	free(data->root);
diff --git a/libselinux/src/label_file.h b/libselinux/src/label_file.h
index d5dd2be1..b59db003 100644
--- a/libselinux/src/label_file.h
+++ b/libselinux/src/label_file.h
@@ -67,11 +67,11 @@ extern struct lookup_result *lookup_all(struct selabel_handle *rec, const char *
 extern enum selabel_cmp_result cmp(const struct selabel_handle *h1, const struct selabel_handle *h2);
 #endif  /* FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION */
 
-/* A path substitution entry */
 struct selabel_sub {
-	char *src;				/* source path prefix */
-	char *dst;				/* substituted path prefix */
-	uint32_t slen;				/* length of source path prefix */
+	char *src;
+	unsigned int slen;
+	char *dst;
+	struct selabel_sub *next;
 };
 
 /* A regular expression file security context specification */
@@ -159,17 +159,9 @@ struct saved_data {
 
 	struct mmap_area *mmap_areas;
 
-	/*
-	 * Array of distribution substitutions
-	 */
+	/* substitution support */
 	struct selabel_sub *dist_subs;
-	uint32_t dist_subs_num, dist_subs_alloc;
-
-	/*
-	 * Array of local substitutions
-	 */
 	struct selabel_sub *subs;
-	uint32_t subs_num, subs_alloc;
 };
 
 void free_spec_node(struct spec_node *node);
@@ -795,6 +787,8 @@ static int insert_spec(const struct selabel_handle *rec, struct saved_data *data
 	return 0;
 }
 
+#undef GROW_ARRAY
+
 /* This will always check for buffer over-runs and either read the next entry
  * if buf != NULL or skip over the entry (as these areas are mapped in the
  * current buffer). */
