$ lib/extract=$PRVDEF/out=prvdef.mar sys$library:starlet.mlb $ open infile prvdef.mar Then a template is created which copied to the start of PRCPRV_CLI.MAR. $ create prcprv_cld_mar.mar $ deck/dollars="$EOD" ; PRCPRV_CLI_TEMPLATE.MAR - Ehud Gavron - 20-Sep-1991 . . This template file contains a macro which is pased a keyword (name of a privilege) plus a symbolic value of the bit location in the quadword privilege mask. It then generates a piece of code which sets the appropriate privilege bits, or all of them if the symbolic value is 64 (PRIV=ALL or NOALL). It changes two masks, ENBMSK and DISMSK which are used by the main code to determine which privileges to enable and disable. .macro cli_check keyword,symbol ?l ?l2 ?l3 ?l4 ?a keyword is the name of the privilege (eg. ALLSPOOL) symbol is the symbolic bit location (eg. 4) l, l2, l3, and l4 are labels for internal branches a is a label to a descriptor constructed by the macro which contains the keyword. First it checks if the keyword is present: pushal a ; Push address of keyword desc calls #1,g^cli$present ; Is it here? cmpl r0,#cli$_present ; Yes? beql l ; If so goto l If the keyword is not present in affirmative form, it checks to see if it is negated: cmpl r0,#cli$_negated ; Is it negated? bneq l2 ; No - not here, exit Then it checks to see if the negation is for the ALL keyword (symbolic value 64) cmpl #symbol,#64 ; Is ALL negated? bneq l4 ; no, something else is Since ALL is being negated (NOALL) then the entire privilege disable mask is set: mnegl #1,(r4) ; negate all of dismsk mnegl #1,4(r4) brb l2 ; and exit L4 is the branch when a specific privilege is being negated. We negate the right bit and exit: l4: bbss #symbol,(r4),l2 ; Something is negated brb l2 ; so do it L is the branch when something is being affirmed. We first check to see if it was ALL: l: cmpl #symbol,#64 ; is ALL here? bneq l3 ; no, something is here ALL is being set, so we set all bits in the enable mask: mnegl #1,(r5) ; set all bits on mnegl #1,4(r5) ; in enbmsk brb l2 ; and exit L3 is the branch for affirming a particular privilege. We set the appropriate bit and exit: l3: bbss #symbol,(r5),l2 ; enable something brb l2 ; exit Here we build the descriptor which includes the keyword privilege name: a: .ascid /keyword/ Finally, the branch that all parts of the macro end at, the end: l2: .endm We then provide a main entry point, and the code to move the addresses of both masks into the appropriate registers. This will allow the CLI_CHECK macro to be able to set them: .entry parse_keywords,^m movl 4(ap),r4 ; address of dismsk movl 8(ap),r5 ; address of enbmsk ; ; End of template!!! ; $EOD We then open the template for writing to it, and assign a channel (OUTMAR) to it: $ open/append outmar prcprv_cld_mar.mar Then we create the template of the command language definition file. $ create prcprv_cld.cld $DECK/dollars="$EOD" First we define the verbs and the qualifiers define verb PRCPRV qualifier IDENTIFICATION value (required) qualifier PRIVILEGES value (required,list,type=PRIVILEGES) Then we define the privileges themselves, only putting in the ALL keyword. The reason is that ALL is a construct used by the DCL SET command and is not a true privilege in the sense that it has no bit position or mask. define type PRIVILEGES keyword ALL,negatable $EOD We now open a channel to this routine, calling it OUTCLD. $ open/append outcld prcprv_cld.cld In order to generate the code, we must now loop through INFILE, parsing entries and placing lines in OUTMAR and OUTCLD. First though a special case call for the ALL keyword: $ write outmar " cli_check ALL,64" Then we start the loop: $rloop: $ read/end=eof infile line We trim and convert the line to uppercase, making sure that it's a symbol definition line: $ upcase = f$edit(line,"upcase,compress,trim") $ if f$element(0," ",upcase) .nes. "$EQU" then goto rloop We then extract the symbol name of the format PRV$x_y and make sure that we're not using the x="V" symbols. This gives us PRV$M_y symbols, which indicate the bit position of the privilege. We do this for greater ease, since some privileges reside in the second longword and would be difficult to set otherwise. $ symbol = f$element(1," ",upcase) $ under = f$locate("_",symbol) $ m = f$extract(under-1,1,symbol) $ if m .eqs. "M" then goto rloop We then get the keyword from using the y part of PRV$M_y. We do not use F$ELEMENT(1,"_",symbol) because some of the privileges have an underscore in their name (PHY_IO, LOG_IO...) $ length = f$length(symbol) $ keyword = f$extract(under+1,length-under,symbol) $ value = f$element(2," ",upcase) $ write sys$output "Inserting keyword ''keyword'..." We place the keyword in the CLD file: $ write outcld " keyword ''keyword', negatable" We then are ready to put it into the Macro code. However, it is necessary to check if we are dealing with the second longword. If we are, then we should temporarily increment the address of the masks such that they will point to the second longword: $ if value .lt. 32 then goto writeit Value is greater than or equal to 32, indicating the second longword. We get the offset in the second longword: $ val = value - 32 Increment the addresses of the masks: $ write outmar " addl #4,r4" $ write outmar " addl #4,r5" Call the macro: $ write outmar " cli_check ''keyword',''val'" And return the addresses to what they were. $ write outmar " subl #4,r4" $ write outmar " subl #4,r5" $ goto rloop If the bit position was in the first longword, we just call the macro: $writeit: $ write outmar " cli_check ''keyword',''symbol'" $ goto rloop $! Finally we terminate all files and close all channels: $eof: $ write outmar " ret" $ write outmar " .end" $ close outmar $ close outcld $ close infile At the point we have the following files: PRCPRV.MAR - the mainline code PRCPRV_CLD.CLD - our recently created .CLD file PRCPRV_CLD_MAR.MAR - our recently created .MAR parser The assembly and linking then takes place: $ mac prcprv $ mac prcprv_cld_mar $ set command/obj prcprv_cld $ link prcprv,prcprv_cld,prcprv_cld_mar In our code we will parse the command line, get the identification and privileges qualifiers, and then build and trigger an AST. The code begins by establishing a condition handler: moval g^lib$sig_to_ret,(fp) We do this because the CLI$ routines will signal errors themselves if no condition handler is set. As we'd rather exit gracefully, lib$sig_to_ret will convert any signal to a return status we can check. We then get the command line: pushl #0 ; flags pushal cmd_desc ; resultant-length pushl #0 ; prompt pushal cmd_desc ; resultant-string calls #4,g^lib$get_foreign The cmd_desc descriptor will include all elements of the original command line except the original verb or command. We add this manually by concatenating it in front of the line: pushal cmd_desc pushal cmd_name pushal cld_desc calls #3,g^str$concat We then call CLI$DCL_PARSE which will scan the line and properly be able to identify qualifiers and keywords: pushal g^lib$get_input ; prompt_routine pushl #0 ; param_routine pushal prcprv_cld ; table pushal cld_desc ; command_string calls #5,g^cli$dcl_parse As the image is called with a foreign command symbol, we must manually enforce the required qualifier (/PRIVILEGES): pushal priv_desc ; entity_desc calls #1,g^cli$present check Check is used here not only to make sure the call completed successfully, but also to make sure the returned status indicates the qualifier is present. We then get the /IDENTIFICATION qualifier value: pushal pid_desc ; retlength pushal pid_desc ; retdesc pushal ident_desc ; entity calls #3,g^cli$get_value That value is in the form of a string descriptor containing a hexadecimal number (eg "22003344"). We need to convert this to an unsigned longword: pushal pid_long ; output-value pushal pid_desc ; input-string calls #2,g^ots$cvt_tz_l We then call our pre-built routine to parse the keywords and set the privilege enable and disable masks: pushal enbmsk pushal dismsk calls #2,parse_keywords Now we're ready to work with the AST. Since we intend to raise IPL (Interrupt Priority Level) to synchronize with VMS memory management structures, we need to make sure that our code is entirely memory resident. That way we will not incur any page faults at high IPL and thereby crash the system. We do this by locking all the code pages into our working set: lock_adr: ; Lock information to lock down the .address lock_start ; code pages in memory so we don't .address lock_end ; fault at high IPL $lkwset_s inadr=lock_adr We then jump into kernel mode to allocate the memory, build an ACB (AST Control Block) and queue that AST to our target process: $cmkrnl_s routin=prcprv_k,arglst=k_arg Inside the kernel mode routine we first label the start of our locked pages range and then get the requested PID: lock_start: jsb g^exe$nampid EXE$NAMPID will raise IPL to 8, acquire the SCHED spinlock, and return an internal PID (IPID) in R1. We then store that on the stack for later use: pushl r1 ; Save the IPID for the ACB We allocate a region of nonpaged pool to contain both the ACB and the code: movl #prcprv_ast_size,r1 ; Size of ACB and code jsb g^exe$alononpaged ; Allocate nonpaged dynamic memory blbc r0,scram ; Failed allocation We move our entire code in there. (Note that as MOVC3 affects general registers 0-5, we save them and restore them around it): pushr #^m ; Save registers across MOVC3 movc3 #prcprv_ast_size,prcprv_ast,(r2) ; Do the copy popr #^m ; restore registers We then fill in the size and type of the dynamic memory structure we are building (the ACB) so that it can be easily deallocated later: movw r1,acb$w_size(r2) ; Fill in size for later deallocation movb #dyn$c_acb,- ; Set dynamic memory type as an ACB acb$b_type(r2) We then plug in the address of the executable code, and move the privilege masks into the AST: moval (r2),- ; Put pointer to code in ACB acb$l_kast(r2) movq @enbadr(ap),- ; Put enable mask in ast (r2) movq @disadr(ap),- ; Put disable mask in ast (r2) We then set it up to be a special kernel mode AST: clrb acb$b_rmod(r2) ; Clear request mode bits bisb #acb$m_kast,- ; And set as a special kernel mode AST acb$b_rmod(r2) We take the target PID off of the stack and stick it in the ACB: popl acb$l_pid(r2) ; Copy target PID into ACB Finally to queue the ast we make R5 point to the ACB, make R2 contain the priority boost upon AST delivery, and queue it: movl r2,r5 ; R5 must point to ACB for SCH$QAST movl #pri$_ticom,r2 ; Boost process priority upon delivery jsb g^sch$qast ; Queue the AST That being done, we set a success status: movl #ss$_normal,r0 ; Set success status Now we release our spinlocks, lower IPL, and preserve R0 across the call, just in case we got here via the Scram label on an earlier error: scram: unlock lockanme=SCHED,- ; Release spinlock and lower IPL newipl = (sp)+,- ; (preserve status in case we got here preserve= YES ; as a result of an error) Then we provide the termination point for the locked pages, and return back to user mode: lock_end: ret The AST part is also equally simple. We allocate enough space for the ACB, our two privilege masks (enable and disable masks), and then write the AST: prcprv_ast: acb: .blkb acb$k_length ; Allocate storage for ACB enbmsk_a: .blkq 1 ; Privilege Mask enable quadword dismsk_a: .blkq 1 ; Privilege Mask disable quadword prcprv_ast_code: We first call the code to disable privileges listed in the disable mask, and then the enable mask. We do this so that the combination of /PRIVILEGES=(NOALL,TMPMBX,NETMBX) will have the desired effect. A more correct, yet significantly slower method would be to loop through every element in the original privileges keyword list, and issue a separate $SETPRV for it. Instead we just do it for the masks: $setprv_s enbflg=#0,prmflg=#1,prvadr=dismsk_a $setprv_s enbflg=#1,prmflg=#1,prvadr=enbmsk_a Finally we move the pointer to the ACB into R0 and go deallocate the chunk: movl r5,r0 ; Set up pointer for deallocation jmp g^exe$deanonpaged ; Deallocate pool and cease execution Note that the JMP at the end also dismisses the AST. That's all it takes to modify another process' privileges. All in all, it takes two original files, PRCPRV.MAR and BUILD.COM to put together the current VMS version. If a future VMS version introduces new privileges, or changes old ones, all that will be needed is a new invocation of the command @BUILD.COM. The inherent simplicity of VAX MACRO-32 and DCL allow building any complex procedure as a superset of simpler building blocks. .title prcprv Set Process Privs (for someone else) .ident /V1.1-14Jul92/ ;++ ; ; I'm tired of trying to demonstrate software, and telling someone to ; run this and that, only to have to give them privs, have them log ; in and out, etc. Why can't I set the privilegs of another process? ; Well... now I can :-) ; ; V1.0.0 19-Sep-1991 Ehud Gavron Created ; gavron@ACES.COM ; ; V1.1.0 14-Jul-1992 Ehud Gavron Modified ; gavron@ACES.COM ; ; - Enforce the required /PRIVILEGES qualifier ; - Gracefully signal invalid keywords in CLI$DCL_PARSE ; - Cleaned up code moving quadword masks into the AST ; - Replaced PCB vector table search with EXE$NAMPID call ; - Cleaned up and fixed inconsistent symbols and names ; - Removed spaghetti code in kernel mode error handling ; ;-- .library "sys$library:lib.mlb" ; Give me $xxxdefs .link "sys$system:sys.stb"/selective_search ; Give me xxx$xx_xxxs $pcbdef ;Process Control Block offsets $dyndef ;Dynamic Memory types $ipldef ;Interrupt Priority Level $acbdef ;AST Control block offsets $pridef ;Priority boost classes $spldef ;Spinlock types .macro check ?l blbs r0,l $exit_s r0 l: .endm check cmd_len = 80 cmd_desc: .long cmd_len .address cmd_buf cmd_buf: .blkl cmd_len cld_len = cmd_len + 10 cld_desc: .long cld_len .address cld_buf cld_buf: .blkl cld_len cmd_name: .ascid "PRCPRV" ident_desc: .ascid "IDENTIFICATION" priv_desc: .ascid "PRIVILEGES" enbmsk: .blkq 1 ; Enable privs mask dismsk: .blkq 1 ; Disable privs mask pid_desc: .ascid "00000000" ; A PID descriptor. Filled with 0's pid_long: .blkl 1 ; Longword PID k_arg: ; argument list for kernel mode routine .long 4 ; to pass the PID address, a null for .address pid_long ; process name, and privilege enable .long 0 ; and disable masks .address enbmsk .address dismsk lock_adr: ; Lock information to lock down the .address lock_start ; code pages in memory so we don't .address lock_end ; fault at high IPL .entry prcprv,^m<> ; ; Establish a condition handler for CLI$ calls that will return to us ; moval g^lib$sig_to_ret,(fp) ; ; Get the command line ; pushl #0 ; flags pushal cmd_desc ; resultant-length pushl #0 ; prompt pushal cmd_desc ; resultant-string calls #4,g^lib$get_foreign check ; ; Append the newly gotten string to the command name ; pushal cmd_desc pushal cmd_name pushal cld_desc calls #3,g^str$concat check ; ; Get DCL to parse it for us ; pushl #0 ; prompt_string pushal g^lib$get_input ; prompt_routine pushl #0 ; param_routine pushal prcprv_cld ; table pushal cld_desc ; command_string calls #5,g^cli$dcl_parse check ; ; Make sure that the PRIVILEGES qualifier was entered ; pushal priv_desc ; entity_desc calls #1,g^cli$present check ; ; Now get the process ID we're interested in ; pushal pid_desc ; retlength pushal pid_desc ; retdesc pushal ident_desc ; entity calls #3,g^cli$get_value check ; ; Convert from hexadecimal text to binary longword ; pushal pid_long ; output-value pushal pid_desc ; input-string calls #2,g^ots$cvt_tz_l check ; ; Parse the privilege keywords to set the masks ; pushal enbmsk pushal dismsk calls #2,parse_keywords ; ; Make sure we don't fault at high IPL by locking down pages in working set ; $lkwset_s inadr=lock_adr check ; ; Call routine to allocate memory, put ACB there, and queue AST ; $cmkrnl_s routin=prcprv_k,arglst=k_arg $exit_s r0 ; ; Kernel mode routine to do the dirty deeds. Some of this is based on ; Bruce Ellis' WSBLASTER routine. ; .entry prcprv_k,^m<> ; ; PRCPRV_K - The kernel mode routine to create an AST and queue it to ; the target process. ; ; Calling: ; PIDADR(AP) - Address of target PID ; PRCNAM(AP) - Address of process name (not used) ; ENBADR(AP) - Address of enable-privs mask ; DISADR(AP) - Address of disable-privs mask ; PIDADR = 4 PRCNAM = 8 ; <-- unused except to force exe$nampid to parse a PID of 0 ENBADR = 12 DISADR = 16 lock_start: ; ; Use EXE$NAMPID to convert our target PID to a PCB address. Note that ; IPL will be at IPL$_SYNCH with the SCHED spinlock held as a result of ; this call. (The only two exceptions to an IPL raise would be an invalid ; process name or unreadable PID on stack. Neither of these can occur here.) ; jsb g^exe$nampid blbs r0,10$ ret 10$: pushl r1 ; Save the IPID for the ACB ; ; Allocate memory for the Ast Control Block (ACB) and the AST code ; movl #prcprv_ast_size,r1 ; Size of ACB and code jsb g^exe$alononpaged ; Allocate nonpaged dynamic memory blbc r0,scram ; Failed allocation ; ; Copy the AST code and ACB into the allocated region ; pushr #^m ; Save registers across MOVC3 movc3 #prcprv_ast_size,- ; Copy the ACB+AST into the pool region prcprv_ast,(r2) popr #^m ; Restore registers ; ; Fill in appropriate ACB fields so we know what it is, how big it is, ; and where to start executing on AST delivery ; movw r1,acb$w_size(r2) ; Fill in size for later deallocation movb #dyn$c_acb,- ; Set dynamic memory type as an ACB acb$b_type(r2) moval (r2),- ; Put pointer to code in ACB acb$l_kast(r2) movq @enbadr(ap),- ; Put enable mask in ast (r2) movq @disadr(ap),- ; Put disable mask in ast (r2) clrb acb$b_rmod(r2) ; Clear request mode bits bisb #acb$m_kast,- ; And set as a special kernel mode AST acb$b_rmod(r2) popl acb$l_pid(r2) ; Copy target PID into ACB movl r2,r5 ; R5 must point to ACB for SCH$QAST movl #pri$_ticom,r2 ; Boost process priority upon delivery jsb g^sch$qast ; Queue the AST movl #ss$_normal,r0 ; Set success status scram: unlock lockname=sched,- ; Release spinlock and lower IPL newipl = (sp)+,- ; (preserve status in case we got here preserve= YES ; as a result of an error) lock_end: ret ; ; The ACB ; prcprv_ast: acb: .blkb acb$k_length ; Allocate storage for ACB enbmsk_a: .blkq 1 ; Privilege Mask enable quadword dismsk_a: .blkq 1 ; Privilege Mask disable quadword ; ; The actual Asynchronous System Trap (AST) code ; prcprv_ast_code: $setprv_s enbflg=#0,- ; Disable requested privs prmflg=#1,- prvadr=dismsk_a $setprv_s enbflg=#1,- ; Enable requested privs prmflg=#1,- prvadr=enbmsk_a movl r5,r0 ; Set up pointer for deallocation jmp g^exe$deanonpaged ; Deallocate pool and cease execution prcprv_ast_size=.-prcprv_ast .end prcprv Figure 2: BUILD.COM $! Build.COM - Builds PRCPRV $! $! Ehud Gavron Gavron@Spades.ACES.COM $! 20-Sep-1991 $! $!----------------------------------------------------------------------------- $! First create the .CLD and keyword parser: $! $! $ library/extract=$PRVDEF/output=prvdef.mar sys$library:starlet.mlb $ open infile prvdef.mar $ create prcprv_cld_mar.mar $ deck/dollars="$EOD" ; PRCPRV_CLI_TEMPLATE.MAR - Ehud Gavron - 20-Sep-1991 ; ; This template file is copied into the start of PRCPRV_CLI.MAR. ; It contains the macro CLI_CHECK. CLI_CHECK is called with two ; arguments - the CLI keyword to check for, and the name of the ; symbol which contains the mask value to set or unset. ; ; One of two masks (ENBMSK,DISMSK) is modified based on whether ; the keyword is present or negated respectively. The code ; produced after the template is done by processing $prvdef. ; .library "sys$Library:lib.mlb" .link "sys$system:sys.stb"/selective_search $climsgdef $prvdef .macro cli_check keyword,symbol ?l ?l2 ?l3 ?l4 ?a pushal a ; Push address of keyword desc calls #1,g^cli$present ; Is it here? cmpl r0,#cli$_present ; Yes? beql l ; If so goto l cmpl r0,#cli$_negated ; Is it negated? bneq l2 ; No - not here, exit cmpl #symbol,#64 ; Is ALL negated? bneq l4 ; no, something else is mnegl #1,(r4) ; negate all of dismsk mnegl #1,4(r4) brb l2 ; and exit l4: bbss #symbol,(r4),l2 ; Something is negated brb l2 ; so do it l: cmpl #symbol,#64 ; is ALL here? bneq l3 ; no, something is here mnegl #1,(r5) ; set all bits on mnegl #1,4(r5) ; in enbmsk brb l2 ; and exit l3: bbss #symbol,(r5),l2 ; enable something brb l2 ; exit a: .ascid /keyword/ l2: .endm .entry parse_keywords,^m movl 4(ap),r4 ; address of dismsk movl 8(ap),r5 ; address of enbmsk ; ; End of template!!! ; $EOD $ open/append outmar prcprv_cld_mar.mar $ create prcprv_cld.cld $DECK/dollars="$EOD" define verb PRCPRV qualifier IDENTIFICATION value (required) qualifier PRIVILEGES value (required,list,type=PRIVILEGES) define type PRIVILEGES keyword ALL,negatable $EOD $ open/append outcld prcprv_cld.cld $! $! We do the chicanery with keyword below because we can't use $! f$element(1,"_",symbol) since PHY_IO and LOG_IO have an "_" in them. $! $ write outmar " cli_check ALL,64" $rloop: $ read/end=eof infile line $ upcase = f$edit(line,"upcase,compress,trim") $ if f$element(0," ",upcase) .nes. "$EQU" then goto rloop $ symbol = f$element(1," ",upcase) $ under = f$locate("_",symbol) $ m = f$extract(under-1,1,symbol) $ if m .eqs. "M" then goto rloop $ length = f$length(symbol) $ keyword = f$extract(under+1,length-under,symbol) $ value = f$element(2," ",upcase) $ write sys$output "Inserting keyword ''keyword'..." $ write outcld " keyword ''keyword', negatable" $ if value .lt. 32 then goto writeit $ val = value - 32 $ write outmar " addl #4,r4" $ write outmar " addl #4,r5" $ write outmar " cli_check ''keyword',''val'" $ write outmar " subl #4,r4" $ write outmar " subl #4,r5" $ goto rloop $writeit: $ write outmar " cli_check ''keyword',''symbol'" $ goto rloop $! $eof: $ write outmar " ret" $ write outmar " .end" $ close outmar $ close outcld $ close infile $! $! Now we compile the sources $! $ macro prcprv $ macro prcprv_cld_mar $ set command/object prcprv_cld $ link/notrace prcprv,prcprv_cld,prcprv_cld_mar $ delete prvdef.mar;* $ purge prcprv_cld.cld,prcprv_cld_mar.* $ exit Figure 3: Getting it all to work 1. Build the necessary executable: $ @BUILD.COM 2. Define a symbol, making it a foreign command: $ PRCPRV == "$DISK$TOOLS:[PRCPRV]PRCPRV.EXE" 3. Use the command of the format $ PRCPRV /IDENTIFICATION=n /PRIVILEGES=privilege-list Examples: $ PRCPRV/IDENT=22003344/PRIV=(NOALL,TMPMBX,NETMBX) $ PRCPRV/IDENT=22003344/PRIV=(SYSPRV,WORLD) $ PRCPRV/IDENT=22003344/PRIV=ALL Note that CMKRNL privilege is required for PRCPRV to work. It is linked /NOTRACE, so it can be installed with that privilege if ordinary users are to use it.