iam:PassRole: how it works, how attackers abuse it, and how to scope it
Key takeaways
iam:PassRoleis 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:PassRoleon 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:PassRolelets 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
Resourceelement. Aniam:PassedToServicecondition 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.
- The caller is allowed to pass that role. An identity-based policy on the caller must allow
iam:PassRolewith the role in theResourceelement, 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. - The role trusts the target service. The role's trust policy must allow the service principal, for example
lambda.amazonaws.comfor 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. - The role and the caller are in the same account. The IAM User Guide states that
iam:PassRoleonly 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.
| Service | Call that passes the role | Parameter carrying the role | Attack Map entry |
|---|---|---|---|
| Lambda | CreateFunction | Role | Pass role to Lambda |
| EC2 | RunInstances | IamInstanceProfile (the instance profile that contains the role) | Pass role to EC2 |
| CloudFormation | CreateStack | RoleARN | Pass role to CloudFormation |
| CodeBuild | CreateProject | serviceRole | Pass 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
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
CreateFunctionmanagement event with the role ARN in its request parameters. The Lambda CloudTrail reference shows the event name asCreateFunction20150331, so a rule should match the prefix of the event name. The same reference notes that theEnvironmentandZipFileparameters 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
Invokeis 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:PassRoleand 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
Resourceelement. Where roles are created often, use an IAM path such asarn: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:AssociatedResourceArnwhere 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
ResourceTagconditions oniam:PassRoledo 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:PassRoleon 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:UpdateFunctionCodereplaces 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:ModifyInstanceAttributeandec2: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:SendCommandruns 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.
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