← All posts IAM

iam:PassRole: how it works, how attackers abuse it, and how to scope it

Key takeaways

  • iam:PassRole is the permission AWS checks when a principal hands an IAM role to a service, for example as the execution role of a Lambda function or the instance profile of an EC2 instance. It has no API call of its own.
  • AWS allows the pass only when three things line up: the caller's policies allow iam:PassRole on that role, the role's trust policy allows the target service, and the role sits in the same account as the caller.
  • Combined with a permission that creates or updates compute, iam:PassRole lets a principal run code as a role it was never granted. In the Pathbreak Attack Map, 55 percent of the privilege escalation paths rely on it.
  • CloudTrail records no event for the pass itself, so detection depends on the create or update call and the request parameter that carries the role ARN.
  • The fix is a specific role ARN or IAM path in the Resource element. An iam:PassedToService condition on a wildcard resource narrows the service but still allows every role that trusts it.

iam:PassRole is the AWS IAM permission that controls which roles a principal can hand to an AWS service, such as the execution role of a Lambda function, the instance profile of an EC2 instance, or the service role of a CodeBuild project. It is also the permission that appears most often in the Pathbreak Attack Map: 55 percent of the privilege escalation paths in the map rely on it, and those paths reach 23 AWS services, because every service that runs workloads on your behalf accepts a role you hand it. It is easy to grant too broadly, since a wildcard resource looks harmless in a policy review and the pass itself never appears in CloudTrail. This post explains what the permission does, the checks AWS makes, how an attacker uses it, what the logs show, and how to scope it so the path closes.

What iam:PassRole is

Many AWS services act with an IAM role that you give them when you configure a resource. A Lambda function runs as its execution role, an EC2 instance gets credentials from its instance profile, and a CodeBuild project calls AWS with its service role. Handing that role to the service is called passing the role, and AWS checks whether the caller is allowed to do it.

That check is iam:PassRole. The AWS documentation is explicit that PassRole is a permission and has no API call, so there is no PassRole operation in the SDK. The role ARN travels as a parameter of another call, such as the Role parameter of Lambda CreateFunction, and the service checks the caller's iam:PassRole permission while it handles that call.

The permission exists to stop a principal from handing a service more access than the principal has. If a developer with no Amazon S3 permissions could pass a role that allows S3, the service would perform those S3 actions on their behalf, which is exactly the escalation AWS warns about on the same page.

The three checks AWS makes when a role is passed

The AWS Security Blog post on PassRole sets out three conditions that must all be true before a service can use a passed role.

  1. The caller is allowed to pass that role. An identity-based policy on the caller must allow iam:PassRole with the role in the Resource element, every condition in that statement must be met, and nothing else may deny it, including service control policies, permissions boundaries, session policies and VPC endpoint policies.
  2. The role trusts the target service. The role's trust policy must allow the service principal, for example lambda.amazonaws.com for a Lambda execution role. AWS notes that in some cases the resource is still created when the trust is missing, but the service then cannot use the role.
  3. The role and the caller are in the same account. The IAM User Guide states that iam:PassRole only passes a role to a service in the same AWS account, so a cross-account design needs a role in the second account that can assume the first.

For an attacker, the second condition defines the target list. Any role in the account whose trust policy names a service the attacker can create resources in is a candidate, and the first condition decides whether the attacker may use it.

Where the PassRole check happens

The check runs whenever a resource is created with a service role, and whenever an existing resource is updated with a new role. Changing a Lambda function's execution role is an update of that kind, so it needs the same permission as creating the function.

Services that use service-linked roles usually create and manage those roles themselves, so no pass takes place. Where a service lets you choose a service-linked role, such as an Amazon EC2 Auto Scaling group created with a named one, AWS checks iam:PassRole in the same way.

The table lists a few common calls that pass a role and the request parameter that carries it. The same parameter is what a detection rule needs to read.

ServiceCall that passes the roleParameter carrying the roleAttack Map entry
LambdaCreateFunctionRolePass role to Lambda
EC2RunInstancesIamInstanceProfile (the instance profile that contains the role)Pass role to EC2
CloudFormationCreateStackRoleARNPass role to CloudFormation
CodeBuildCreateProjectserviceRolePass role to CodeBuild

The map lists every pass Pathbreak tracks, including Glue jobs, SageMaker training jobs, ECS tasks, Step Functions and Bedrock AgentCore, and the Attack Map can be filtered to iam:PassRole to see them together.

How attackers abuse iam:PassRole

01 Attacker principal CreateFunction 02 New Lambda iam:PassRole 03 Passed role runs as role 04 Administrator
With iam:PassRole and lambda:CreateFunction held together, a principal creates a function, hands it a privileged role, and the function runs as that role. The pass never appears in CloudTrail as its own event.

The abuse follows the same three steps whichever service is involved.

Find a privileged role that trusts a service. iam:ListRoles returns each role's trust policy in the AssumeRolePolicyDocument field, so one call shows which roles trust Lambda, EC2, CodeBuild or any other service. The attacker looks for a role with broad permissions whose trust policy names a service they can create resources in.

Create a resource with that role attached. With iam:PassRole on the role and lambda:CreateFunction, the attacker creates a function whose execution role is the privileged role. AWS accepts the pass because the caller's policy allows it and the role trusts lambda.amazonaws.com.

Run code as the role. The attacker invokes the function, and whatever code they wrote runs with the privileged role's permissions. If the role is an administrator, a single create and a single invoke take the account. The same move works through EC2 user data, a CodeBuild buildspec, a CloudFormation template or a Glue job script, which is why one over-broad iam:PassRole grant can open many paths at once.

The Lambda version of this chain, with the CloudTrail events for each step, is covered in three AWS privilege escalation attacks, with the CloudTrail evidence and the fix.

Why iam:PassedToService on its own is not enough

iam:PassedToService is a condition key that limits which service a role may be passed to. It is useful, and AWS's own policy validation raises a security warning for iam:PassRole with a wildcard resource and recommends either specific role ARNs or this condition. The two options are far from equal, though, because the condition narrows the service while the wildcard still covers every role in the account.

The AWS managed policy AWSLambda_FullAccess shows the gap clearly. It grants lambda:*, iam:ListRoles and iam:GetRole, and it allows iam:PassRole on "Resource": "*" with iam:PassedToService set to lambda.amazonaws.com. A principal with this policy can list the roles in the account, find any role whose trust policy allows Lambda, create a function with it and invoke it. If a privileged role in the account trusts Lambda, the condition does nothing to stop the escalation, because the pass really is to Lambda.

The condition earns its place when it is combined with a specific resource. A specific role ARN decides which role can be passed, and iam:PassedToService decides which service it can be passed to, which matters when a role trusts more than one service.

How to detect iam:PassRole abuse in CloudTrail

AWS states that no CloudTrail logs are generated for PassRole, so the evidence is always the create or update call that received the role.

  • Watch the parameter. For Lambda, that is the CreateFunction management event with the role ARN in its request parameters. The Lambda CloudTrail reference shows the event name as CreateFunction20150331, so a rule should match the prefix of the event name. The same reference notes that the Environment and ZipFile parameters are left out of the log, so the code the attacker uploaded is not in the event.
  • Compare the role with the caller. The strong signal is a create call that attaches a role with more permissions than the principal making the call, especially from a principal with no history of creating that resource type.
  • Turn on the follow-on events. Lambda Invoke is a data event, and CloudTrail does not log it by default, as AWS confirms. Enabling Lambda data events lets a rule pair the create with an invoke a few minutes later.
  • Read the failures. CloudTrail records failed calls with an error code and error message. When a pass is refused, the error AWS returns names iam:PassRole and the role ARN, as shown in AWS's troubleshooting guidance. A run of denied create calls that each name a different role is what an attacker testing which roles they can pass looks like.

After the pass succeeds, the role's own activity is the last place to look. API calls made as the passed role from a function or instance that did not exist an hour earlier deserve a closer look.

How to scope iam:PassRole

The goal is that each principal can pass only the roles its job needs, to only the services that use them.

  • Name the roles. Put specific role ARNs in the Resource element. Where roles are created often, use an IAM path such as arn:aws:iam::111122223333:role/application_role/* or a name prefix, so new roles in that path are covered without a policy change, as the AWS Security Blog recommends.
  • Add iam:PassedToService. Use it alongside a specific resource, so a role that trusts several services can only be passed to the one the workload uses.
  • Use iam:AssociatedResourceArn where it fits. This condition key limits which resource a role can be attached to. Because some resource ARNs change when a resource is recreated, AWS notes that many organizations restrict who can modify the resource instead.
  • Avoid tag-based control. AWS warns that ResourceTag conditions on iam:PassRole do not give reliable results, so tags on the role are the wrong control for this permission.
  • Protect privileged paths with an SCP. A service control policy can deny iam:PassRole on a protected path, for example the roles a security team uses, unless the caller carries a specific tag or sits in an approved path.
  • Bound roles that developers create. If developers can create their own roles, require a permissions boundary on those roles and allow them to pass only roles in a dedicated path, so a role they create and then pass cannot exceed the boundary.

Each change keeps the capability the team relies on and closes the paths that run through roles they never needed to pass.

What scoping PassRole does not fix

Scoping iam:PassRole closes the paths that start with a new resource. It does nothing for resources that already have a privileged role attached, because changing what those resources run never passes a role. These paths need their own controls on the modify permissions.

  • Lambda function code. lambda:UpdateFunctionCode replaces the code of an existing function and leaves its execution role in place, so the attacker's code runs as that role the next time the function is invoked.
  • EC2 user data. With ec2:StopInstances, ec2:ModifyInstanceAttribute and ec2:StartInstances, an attacker stops an instance, rewrites its user data and starts it again. User data only runs on the first boot by default, so the attacker supplies a cloud-init configuration that runs on every boot, and the script runs under the instance's existing profile.
  • SSM commands. ssm:SendCommand runs a script on a managed instance, which can read the credentials of the role already attached to it.
  • CloudFormation stack roles. AWS notes that once a service role is attached to a stack, users who can operate on the stack can use the role whether or not they have iam:PassRole, so a principal that can update the stack or execute a change set inherits the role. The UpdateStack variant works the same way.
  • CodeBuild projects. Starting a build of an existing project with an overridden buildspec runs the attacker's commands under the project's service role.

An account review that stops at iam:PassRole will miss all of these, which is why the map lists them as separate entries.

How to check who can pass a privileged role

For one principal and one role, the IAM policy simulator gives a quick answer. It evaluates identity-based policies and service control policies, including conditions, for the principal you name.

aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::111122223333:user/dev-user \
--action-names iam:PassRole \
--resource-arns arn:aws:iam::111122223333:role/AdminRole

Where the principal's policy uses iam:PassedToService, add a --context-entries argument that sets the iam:PassedToService key to the target service, such as lambda.amazonaws.com, because without it the simulator has no value to compare against. The result answers only the first of the three checks. Whether the role's trust policy allows the service is a separate question, answered by reading the role's AssumeRolePolicyDocument, and the caller still needs the create permission for that service. AWS also notes that simulator results can differ from the live environment.

That is manageable for a single pair and impractical across an account with hundreds of principals and roles. The free, open-source pathbreak-audit tool reads the IAM in one account and lists the roles and users that carry abusable permissions and combinations such as iam:PassRole with a create action, with the fix for each, as a starting point. Pathbreak goes further, resolving every pass against the caller's policies, boundaries, SCPs and the role's trust policy, so only the paths that would actually succeed are reported, ranked by what the passed role can reach.

Frequently asked questions

What does iam:PassRole do?

iam:PassRole is the permission AWS checks when a principal gives an IAM role to a service, for example as a Lambda function's execution role or an EC2 instance profile. It controls which roles a principal may hand to services, so the service never acts with more access than the principal was meant to have. Pathbreak shows which principals in your accounts can pass each privileged role.

Is iam:PassRole dangerous on its own?

Not on its own. It becomes a privilege escalation path when it is combined with a permission that creates or updates a resource that accepts a role, such as lambda:CreateFunction or ec2:RunInstances, and when a privileged role trusts that service. Scoping the Resource element to specific roles removes the path. Pathbreak checks the trust policy and conditions for each pass when it validates a path.

Does CloudTrail log iam:PassRole?

No. AWS documents that PassRole is a permission and generates no CloudTrail event. The pass appears as a parameter of the call that created or modified the resource, such as the role ARN in a Lambda CreateFunction event, and refused passes appear as failed create calls whose error message names iam:PassRole.

Is iam:PassedToService enough to make iam:PassRole safe?

Not when the resource is a wildcard. The condition limits the service, but every role in the account that trusts that service can still be passed, which is the case with the AWSLambda_FullAccess managed policy. Combine iam:PassedToService with specific role ARNs or an IAM path.

How do I find who can pass a privileged role in my account?

For a single principal, aws iam simulate-principal-policy with iam:PassRole and the role ARN answers whether the principal's policies allow the pass, and the role's trust policy shows which services can receive it. Across a whole account, pathbreak-audit lists the principals with these permissions, and Pathbreak validates which passes would succeed and ranks them by what they reach.

Related posts

Find out which principals in your accounts can pass a privileged role today, and the one change that closes each path.

Request a demo Watch the demo