diff --git a/doc/program_flow b/doc/program_flow
index 4137cf0..5fbdb5d 100644
--- a/doc/program_flow
+++ b/doc/program_flow
@@ -62,7 +62,7 @@ libpseudo execution flow:
 			return enosys
 		variadic setup (if necessary)
 		if pseudo_disabled  return real_*()
-		pseudo_sigblock
+		pseudo_sigblock (unless this thread already holds the lock)
 		pseudo_getlock
 		if antimagic  rc = real_*()
 		else
@@ -74,7 +74,7 @@ libpseudo execution flow:
 				drop path lock
 		variadic end (if necessary)
 		pseudo_droplock
-		unmask signals
+		unmask signals (if they were blocked above)
 		return rc
 
 pseudo execution flow:
diff --git a/pseudo_wrappers.c b/pseudo_wrappers.c
index a4e9015..13d7ede 100644
--- a/pseudo_wrappers.c
+++ b/pseudo_wrappers.c
@@ -58,6 +58,7 @@ static pthread_t pseudo_mutex_holder;
 static int pseudo_mutex_recursion = 0;
 static int pseudo_getlock(void);
 static void pseudo_droplock(void);
+static int pseudo_lock_held(void);
 static size_t pseudo_dechroot(char *, size_t);
 static void pseudo_sigblock(sigset_t *);
 
@@ -226,6 +227,11 @@ pseudo_getlock(void) {
 	}
 }
 
+static int
+pseudo_lock_held(void) {
+	return pthread_equal(pseudo_mutex_holder, pthread_self());
+}
+
 static void
 pseudo_droplock(void) {
 	if (--pseudo_mutex_recursion == 0) {
diff --git a/templates/wrapfuncs.c b/templates/wrapfuncs.c
index d7fb548..2fa4212 100644
--- a/templates/wrapfuncs.c
+++ b/templates/wrapfuncs.c
@@ -20,6 +20,7 @@ ${maybe_skip}
 ${type}
 ${name}(${decl_args}) {
 	sigset_t saved;
+	int nested;
 	${variadic_decl}
 	${rc_decl}
 	${paths_decl}
@@ -44,11 +45,17 @@ ${maybe_async_skip}
 	}
 
 	pseudo_debug(PDBGF_WRAPPER, "wrapper called: ${name}\n");
-	pseudo_sigblock(&saved);
+	/* If this thread already holds the lock, an outer wrapper has
+	 * already blocked signals, so don't block and restore them again.
+	 */
+	nested = pseudo_lock_held();
+	if (!nested)
+		pseudo_sigblock(&saved);
 	pseudo_debug(PDBGF_WRAPPER | PDBGF_VERBOSE, "${name} - signals blocked, obtaining lock\n");
 	if (pseudo_getlock()) {
 		errno = EBUSY;
-		sigprocmask(SIG_SETMASK, &saved, NULL);
+		if (!nested)
+			sigprocmask(SIG_SETMASK, &saved, NULL);
 		pseudo_debug(PDBGF_WRAPPER, "${name} failed to get lock, giving EBUSY.\n");
 		PROFILE_DONE;
 		${def_return}
@@ -66,8 +73,11 @@ ${maybe_async_skip}
 			pseudo_debug(PDBGF_SYSCALL, "${name} ignored path, calling real syscall.\n");
 			${rc_assign} (*real_${name})(${call_args});
 		} else {
-			/* exec*() use this to restore the sig mask */
-			pseudo_saved_sigmask = saved;
+			/* exec*() use this to restore the sig mask; a
+			 * nested call keeps the outer wrapper's mask.
+			 */
+			if (!nested)
+				pseudo_saved_sigmask = saved;
 			${path_lock_begin}${rc_assign} wrap_$name(${call_args});${path_lock_end}
 		}
 	}
@@ -76,7 +86,8 @@ quick_exit:
 	__attribute__((unused));
 	save_errno = errno;
 	pseudo_droplock();
-	sigprocmask(SIG_SETMASK, &saved, NULL);
+	if (!nested)
+		sigprocmask(SIG_SETMASK, &saved, NULL);
 	pseudo_debug(PDBGF_WRAPPER | PDBGF_VERBOSE, "${name} - yielded lock, restored signals\n");
 	/* not strerror(), which can hang on systems using locale data for it */
 	pseudo_debug(PDBGF_WRAPPER, "wrapper completed: ${name} returns ${rc_format} (errno: %d)\n", ${rc_value}, save_errno);
