SharePoint Adventures : AccessDeniedException when a user tries to Add Subscription

I’ve been seeing a few cases that relate to a previous blog post which involved an AccessDeniedException when trying to perform report operations.  However, I ran into a different variation that involved the same error. 

The scenario was that the customer has users that were in a the Viewer role within SharePoint.  This could also be the case if the user only had Read permissions within the SharePoint site. When they went to “Manage Subscriptions” for a report in a document library, and then click “Add Subscription”, they received the following error:

Throwing Microsoft.ReportingServices.Diagnostics.Utilities.AccessDeniedException: , Microsoft.ReportingServices.Diagnostics.Utilities.AccessDeniedException: The permissions granted to user 'BATTLESTAR\badama' are insufficient for performing this operation.;   

At face value, my reaction was that this is expected.  I wouldn’t expect a user in a Viewer Role or with only Read rights to be able create a subscription. This made sense.  Until I was pointed to the following documentation for Reporting Services 2012:

Compare Roles and Tasks in Reporting Services to SharePoint Groups and Permissions
https://msdn.microsoft.com/en-us/library/bb283182.aspx

Browser

View reports and self-manage individual subscriptions.

Use the Visitors group to grant permissions to view reports and create subscriptions. The Visitors group has Read level permissions, which enables group members to view pages, list items, and documents.

Interesting.  Also interesting, is that the customer indicated this was working with the 2008 R2 version.  So, something changed.  Either this was an intended change and the documentation just didn’t get updated (which was my initial assumption), or an unintended change occurred that shouldn’t have, and the documentation was still accurate.

After some debugging, I found that we had changed the permission type that we check for.

SPBasePermissions Enumeration
https://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spbasepermissions.aspx

2008 R2 Version
ViewListItems - View items in lists, documents in document libraries, and view Web discussion comments.

2012 Version
EditListItems - Edit items in lists, edit documents in document libraries, edit Web discussion comments in documents, and customize Web Part Pages in document libraries.

This change was done for security reasons, which makes sense.  The thought behind this was also that, in most organizations, creating a subscription is a relatively high-privileged operation since it could have some performance impact and could affect the security of the data and stability of the server.

As a result, the referenced documentation above, for Reporting Services, is going to be updated to reflect this change.

 

Techie Details

To determine what was going on, I captured a dump using DebugDiag and set a Crash Dump Rule for all IIS processes on the box.  I set the Exception Rule to a .NET 1.0-3.5 exception and set the Exception Name to AccessDeniedException as that’s what was in our error message.

Once I have the dump and open it up using WinDBG, I issue the following command to load the .NET Debugger Extension that ships with the .NET Framework.  This is something you can do on your own machine.

0:055> .loadby sos mscorwks

Then we can verify that this dump actually grabbed the right error.

0:055> !pe
Exception object: 0000000103ad3dd0
Exception type: Microsoft.ReportingServices.Diagnostics.Utilities.AccessDeniedException
Message: The permissions granted to user 'BATTLESTAR\badama' are insufficient for performing this operation.
InnerException: <none>
StackTrace (generated):
<none>
StackTraceString: <none>
HResult: 80131500

We have the right error.  Let’s see what the call stack is.

0:055> !clrstack
OS Thread Id: 0x1128 (55)
Child-SP RetAddr Call Site
0000000011d8cc10 000007ff01779e15 Microsoft.ReportingServices.Library.BaseExecutableCatalogItem.ThrowIfNoAccess(Microsoft.ReportingServices.Interfaces.ReportOperation)
0000000011d8cc70 000007ff01768a60 Microsoft.ReportingServices.Library.GetExecutionOptionsAction.PerformActionNow()
0000000011d8cd10 000007ff017799c1 Microsoft.ReportingServices.Library.RSSoapAction`1[[System.__Canon, mscorlib]].Execute()
0000000011d8cdc0 000007ff017798d4 Microsoft.ReportingServices.Library.ReportingService2005Impl.GetExecutionOptions(System.String, Microsoft.ReportingServices.Library.Soap.ExecutionSettingEnum ByRef, Microsoft.ReportingServices.Library.Soap.ScheduleDefinitionOrReference ByRef)
0000000011d8ce30 000007ff0028d8a9 Microsoft.ReportingServices.ServiceRuntime.ReportServiceManagement+<>c__DisplayClassee.<GetExecutionOptions>b__ed()
0000000011d8ce70 000007ff002c7b9f Microsoft.ReportingServices.ServiceRuntime.ReportServiceBase.ExecuteWithContext[[System.__Canon, mscorlib]](System.Func`1<System.__Canon>)
0000000011d8cec0 000007fee835bd28 DynamicClass.SyncInvokeGetExecutionOptions(System.Object, System.Object[], System.Object[])

One thing to note here, to tell us we are on the right track, you’ll notice the GetExecutionOptionsAction call.  We saw this a few lines above the error in the SharePoint ULS log.

Call to RSGetExecutionOptionsAction().  

So, this lines up.  We see this calling into ThrowIfNoAccess, and there is a ReportOperation being passed in.  We can try to look at the stack objects to see if we can tell what the ReportOperation is that is being passed in.

0:055> !dso
OS Thread Id: 0x1128 (55)
RSP/REG Object Name
rbp 0000000103ad3168 Microsoft.ReportingServices.Library.ProfessionalReportCatalogItem
0000000011d8c938 0000000103ad5ac8 Microsoft.ReportingServices.Diagnostics.ContextBody
0000000011d8c940 0000000103ad59f8 Microsoft.SqlServer.SqlDumper.Dumper
...
0000000011d8cbb0 0000000103ad4088 System.String
0000000011d8cbc0 00000001039d50b0 System.String
0000000011d8cbc8 0000000103ad3368 Microsoft.ReportingServices.SharePoint.Server.SharePointSecurity
0000000011d8cbe8 0000000103ad4218 System.Object[] (System.Object[])
0000000011d8cbf8 00000001039d50b0 System.String
0000000011d8cc00 0000000103ad3dd0 Microsoft.ReportingServices.Diagnostics.Utilities.AccessDeniedException
0000000011d8cc10 0000000103ad3dd0 Microsoft.ReportingServices.Diagnostics.Utilities.AccessDeniedException
0000000011d8cc18 00000001039d50b0 System.String
0000000011d8cc20 00000001409202b0 System.String
0000000011d8cc28 000000014048b5c0 Microsoft.ReportingServices.Diagnostics.Utilities.RSTrace
0000000011d8cc30 0000000103a2e410 Microsoft.ReportingServices.Diagnostics.ExternalItemPath
0000000011d8cc38 0000000103a2af30 Microsoft.ReportingServices.Diagnostics.CatalogItemContext
0000000011d8cc40 0000000103a2a040 Microsoft.ReportingServices.Library.GetExecutionOptionsAction
0000000011d8cc48 0000000103ad3168 Microsoft.ReportingServices.Library.ProfessionalReportCatalogItem
0000000011d8cc50 0000000103a25498 Microsoft.ReportingServices.Library.RSService
0000000011d8cc58 0000000103a2af30 Microsoft.ReportingServices.Diagnostics.CatalogItemContext
0000000011d8cc70 0000000103ad3168 Microsoft.ReportingServices.Library.ProfessionalReportCatalogItem

Unfortunately, nothing looks like the ReportOperation. At this point, we can’t tell what right we are trying to validate, and why it caused the AccessDeniedException.  At this point, we can drop to the code itself.  We can use the !savemodule command to output the .NET Assembly, and then we can use something like Telerik’s JustDecompile to have a look.

The first .NET Assembly we are interested in is ReportingServicesLibrary.dll.  We can find this within the dump this way.

0:055> lmvm ReportingServicesLibrary
start end module name
00000000`68f80000 00000000`69190000 ReportingServicesLibrary
Loaded symbol image file: ReportingServicesLibrary.DLL
Image path: C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\WebServices\RSTempFiles\797da953fbf941e79d445a1791e8fa32\b8ec940d\7ebb764f\assembly\dl3\f0a7062f\000cc91d_1b14cd01\ReportingServicesLibrary.DLL
Image name: ReportingServicesLibrary.DLL
Using CLR debugging support for all symbols
Has CLR image header, track-debug-data flag not set
Timestamp: Fri Apr 06 05:35:20 2012 (4F7EC6E8)
CheckSum: 00213370
ImageSize: 00210000
File version: 11.0.2316.0
Product version: 11.0.2316.0

We can then use the Module Start Address (00000000`68f80000) for the !savemodule command and give it a path to save the module.

0:055> !savemodule 00000000`68f80000 c:\temp\files\ReportingServicesLibrary.dll
3 sections in file
section 0 - VA=2000, VASize=208824, FileAddr=1000, FileSize=209000
section 1 - VA=20c000, VASize=570, FileAddr=20a000, FileSize=1000
section 2 - VA=20e000, VASize=c, FileAddr=20b000, FileSize=1000

Within JustDecompile, we are going to look for Microsoft.ReportingServices.Library.GetExecutionOptionsAction.PerformActionNow().  This is because it is calling the ThrowIfNoAccess, and we want to see if we can pick off what it is passing in for the parameter.

I skipped the load for Microsoft.ReportingServices.Diagnostics, but grabbed the assembly for Microsoft.ReportingServices.Interfaces

0:055> lmvm Microsoft_ReportingServices_Interfaces
start end module name
00000000`697d0000 00000000`697e2000 Microsoft_ReportingServices_Interfaces (deferred)
Image path: C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\WebServices\RSTempFiles\797da953fbf941e79d445a1791e8fa32\b8ec940d\7ebb764f\assembly\dl3\c45b2c1c\00f9ce0d_bee8cc01\Microsoft.ReportingServices.Interfaces.DLL
Image name: Microsoft.ReportingServices.Interfaces.DLL
Using CLR debugging support for all symbols
Has CLR image header, track-debug-data flag not set
Timestamp: Fri Feb 10 21:47:30 2012 (4F35E4D2)
CheckSum: 0001FA3C
ImageSize: 00012000
File version: 11.0.2100.60
Product version: 11.0.2100.60

0:055> !savemodule 00000000`697d0000 c:\temp\files\Microsoft.ReportingServices.Interfaces.dll
3 sections in file
section 0 - VA=2000, VASize=aa74, FileAddr=1000, FileSize=b000
section 1 - VA=e000, VASize=598, FileAddr=c000, FileSize=1000
section 2 - VA=10000, VASize=c, FileAddr=d000, FileSize=1000

From there we can expand the PerformActionNow() method and see the code.

internal override void PerformActionNow()
{
ExecutionSettingEnum executionSettingEnum = ExecutionSettingEnum.Live;
ScheduleDefinitionOrReference scheduleDefinitionOrReference = null;
CatalogItemContext catalogItemContext = new CatalogItemContext(base.Service, base.ActionParameters.ReportPath, "report");
CatalogItem catalogItem = base.Service.CatalogItemFactory.GetCatalogItem(catalogItemContext);
catalogItem.ThrowIfWrongItemType(ItemType.Report, ItemType.LinkedReport);
BaseReportCatalogItem baseReportCatalogItem = catalogItem as BaseReportCatalogItem;
baseReportCatalogItem.ThrowIfNoAccess(ReportOperation.ReadPolicy); <—we are passing ReportOperation.ReadPolicy
base.Service.ExecCacheDb.GetExecutionOptions(catalogItemContext.CatalogItemPath, baseReportCatalogItem.ItemID, out executionSettingEnum, out scheduleDefinitionOrReference);
this.ActionParameters.ExecutionSettings = executionSettingEnum;
this.ActionParameters.Schedule = scheduleDefinitionOrReference;
}

So, we can see that we are passing ReportOperation.ReadPolicy.  This ReadPolicy is Reporting Services view of the permission. But, what is ThrowIfNoAccess doing to compare it.  We can click on that within JustDecompile to see what it is doing.

internal void ThrowIfNoAccess(ReportOperation operation)
{
if (base.Service.SecMgr.CheckAccess(base.ThisItemType, base.SecurityDescriptor, operation, base.ItemContext.ItemPath))
{
return;
}
else
{
throw new AccessDeniedException(base.Service.UserName); <—this is what threw the error that we captured.
}
}

The IF statement is what is returning false, which then throws the AccessDeniedException.  Clicking on the CheckAccess method leads us into the Security Class and one of the overloads for CheckAccess, but due to the decompile, it isn’t very useful.

Lets go back to the ReadPolicy to see what we can get from that.  Clicking on ReportOperation will show us the enumeration itself.  We can then click on ReadPolicy.

public const ReportOperation ReadPolicy = 20;

We will probably want to do a search by Symbol or FullText, but at first try you will only really see the enumeration itself.  However, we know that this is being used for a SharePoint operation, so lets go grab the SharePoint Assemblies that Reporting Services uses to see if that contains the usage.  Because we aren’t sure, the first two modules that looks interesting are Microsoft_ReportingServices_SharePoint_Common and Microsoft_ReportingServices_SharePoint_Server.

0:055> lmvm Microsoft_ReportingServices_SharePoint_Common
start end module name
00000000`6f7d0000 00000000`6f7f6000 Microsoft_ReportingServices_SharePoint_Common (deferred)
Image path: C:\Windows\assembly\GAC_MSIL\Microsoft.ReportingServices.SharePoint.Common\11.0.0.0__89845dcd8080cc91\Microsoft.ReportingServices.SharePoint.Common.dll
Image name: Microsoft.ReportingServices.SharePoint.Common.dll
Using CLR debugging support for all symbols
Has CLR image header, track-debug-data flag not set
Timestamp: Fri Feb 10 21:49:29 2012 (4F35E549)
CheckSum: 00029204
ImageSize: 00026000
File version: 11.0.2100.60
Product version: 11.0.2100.60

0:055> !savemodule 00000000`6f7d0000 c:\temp\files\Microsoft.ReportingServices.SharePoint.Common.dll
3 sections in file
section 0 - VA=2000, VASize=1f4f4, FileAddr=1000, FileSize=20000
section 1 - VA=22000, VASize=5b0, FileAddr=21000, FileSize=1000
section 2 - VA=24000, VASize=c, FileAddr=22000, FileSize=1000

0:055> lmvm Microsoft_ReportingServices_SharePoint_Server
start end module name
00000000`69790000 00000000`697c8000 Microsoft_ReportingServices_SharePoint_Server (deferred)
Image path: C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\WebServices\RSTempFiles\797da953fbf941e79d445a1791e8fa32\b8ec940d\7ebb764f\assembly\dl3\c5ea2cc7\00f9af03_c4e8cc01\Microsoft.ReportingServices.SharePoint.Server.DLL
Image name: Microsoft.ReportingServices.SharePoint.Server.DLL
Using CLR debugging support for all symbols
Has CLR image header, track-debug-data flag not set
Timestamp: Fri Feb 10 21:49:35 2012 (4F35E54F)
CheckSum: 000478B9
ImageSize: 00038000
File version: 11.0.2100.60
Product version: 11.0.2100.60

0:055> !savemodule 00000000`69790000 c:\temp\files\Microsoft.ReportingServices.SharePoint.Server.dll
3 sections in file
section 0 - VA=2000, VASize=30b54, FileAddr=1000, FileSize=31000
section 1 - VA=34000, VASize=5f0, FileAddr=32000, FileSize=1000
section 2 - VA=36000, VASize=c, FileAddr=33000, FileSize=1000

After those are saved, you can open them within JustDecompile and then perform the FullText search for ReadPolicy.

image

The first two items are enumerations and not really helpful.  The next item, labeled InitializeMaps(), was interesting.  Although this prompted us for Microsoft.ReportingServices.SharePoint.ObjectModel.dll.

0:055> lmvm Microsoft_ReportingServices_SharePoint_ObjectModel
start end module name
00000000`697f0000 00000000`69800000 Microsoft_ReportingServices_SharePoint_ObjectModel (deferred)
Image path: C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\WebServices\RSTempFiles\797da953fbf941e79d445a1791e8fa32\b8ec940d\7ebb764f\assembly\dl3\13fb0569\00f9af03_c4e8cc01\Microsoft.ReportingServices.SharePoint.ObjectModel.DLL
Image name: Microsoft.ReportingServices.SharePoint.ObjectModel.DLL
Using CLR debugging support for all symbols
Has CLR image header, track-debug-data flag not set
Timestamp: Fri Feb 10 21:49:36 2012 (4F35E550)
CheckSum: 000177BB
ImageSize: 00010000
File version: 11.0.2100.60
Product version: 11.0.2100.60

0:055> !savemodule 00000000`697f0000 c:\temp\files\Microsoft.ReportingServices.SharePoint.ObjectModel.dll
3 sections in file
section 0 - VA=2000, VASize=80f4, FileAddr=1000, FileSize=9000
section 1 - VA=c000, VASize=630, FileAddr=a000, FileSize=1000
section 2 - VA=e000, VASize=c, FileAddr=b000, FileSize=1000

AuthzData.m_RptOper2PermMask.Add(ReportOperation.ReadPolicy, (uint)1048576);

It is mapping ReadPolicy to another value – although we can’t tell what that really is.  Looking at the second InitializeMaps(), we can see the following.

SharePointAuthzData.m_RsReportOperationToSpRight.Add(ReportOperation.ReadPolicy, (RSSPBasePermissions)((long)4));

This is within Microsoft.ReportingServices.SharePoint.Server within the Type SharePointAuthzData. This one is interesting on two fronts.  It shows RSReportOperationToSpRight, which is what we are interested in.  We ultimately want to see what SharePoint Right we are trying to represent.  The other piece is the RSSPBasePermissions with a Long value of 4.  We can click on RSSPBasePermissions to see what that shows.  It is another enumeration.

EditListItems = 4,

And that is our SharePoint permission that links to the SPBasePermission documentation listed at the beginning of the blog.

If you were to follow these same steps (although we know where to go look now) on the 2008 R2 side, you would see that ReadPolicy maps to ViewListItems (1) instead of EditListItems (4), which confirms the change.

 

Adam W. Saxton | Microsoft Escalation Services
https://twitter.com/awsaxton