Call: +44 (0)7759 277220 Call
PeteFinnigan.com Limited Products, Services, Training and Information
Blog

Pete Finnigan's Oracle Security Weblog

This is the weblog for Pete Finnigan. Pete works in the area of Oracle security and he specialises in auditing Oracle databases for security issues. This weblog is aimed squarely at those interested in the security of their Oracle databases.

Extreme PL/SQL - Running an Assembly Language Program in PL/SQL

Back in 2022 I started work on an idea that I could build an interpreter for a simple language in PL/SQL and then building on that create a Virtual Machine in PL/SQL and an assembler in PL/SQL for the assembly language used by the virtual machine and then create a compiler also written in PL/SQL that could be used to compile a simple language into Assembler that is then assembled into machine code and run in the VM written in PL/SQL.

The article from 2022 is Adding Scripting Languages to PL/SQL Applications - Part 1

Back in 2024 I did a lot of work on the interpreter written in PL/SQL and created a couple of blog posts that showed some simple programs being executed in the interpreter.

The links from 2024 are Extreme PL/SQL - An Interpreter for a Simple Language and Write An Interpreter in PL/SQL - Adding More Features

I went on to complete that interpreter and I have around 150 pages of notes that I will publish over the coming months as a set of articles. Watch out for that.

I have also completed the virtual machine in written in PL/SQL and a test suite for that machine as well as writing an assembler also written in PL/SQL and a test suite for the assembler.

Why do i want to do all of this?

I write in C and have a number of systems / tools that are very powerful as I embed the Lua engine into C so that some of the functionality can be scripted at run time. For instance our obfuscator for dynamic obfuscation uses Lua to do this. This is a fantastic model. I wanted to be able to do something similar in PL/SQL applications so that the application could be extended at run time via scripts. I know PL/SQL could be used for this BUT if you allowed an end user to write random PL/SQL at run time and have it executed by your application that is a recipe for disaster. A better approach is for the original PL/SQL developer to embed a script engine and expose only what it needs of the original application to the end user script writer. This could be specific data or specific functions or procedures.

I will demo the script embedding soon in a blog here. I will be releasing a set of articles about the interpreter as I said above as well as a set of articles about the VM, assembler and compiler and finally a set of articles about embedding a script engine in your PL/SQL.

Today I want to show a simple example of executing assembly language program from PL/SQL. I have created a simple assembler program that calculates the 5th factorial. This program is written in assembler and is assembled to machine code for my VM written in PL/SQL. The assembler program is here:

'; --- Caller ---
MOV 15, r0, -10 ; r0 = 5 (parameter)
LDA r1, fact ; r1 = address of fact
BRA r1, r5 ; CALL fact
COP r0, r2 ; r2 = return value
HLT

; --- Subroutine ---
fact:
COP r0, r3 ; r3 = n (preserve parameter)
MOV 15, r0, -14 ; r0 = 1 (result accumulator)

floop:
MUL r3, r0 ; r0 = r0 * r3 (result *= n)
MOV r3, r3, -1 ; r3 = r3 - 1 (decrement n; sets flag)
BRN floop ; if n != 0, continue

COP r0, r4 ; r4 = result
COP r5, r0 ; r0 = return address (from r5)
BRA r0, r5 ; RETURN'

And embedding the program into a simple PL/SQL harness to run it is here:

-- test_fact_5.sql
-- Pete Finnigan
-- 15.09.2026
-- Test PFCL_ASM with a simple factorial program

declare
l_asm varchar2(32767);
l_out varchar2(32767);
l_result number;
begin
-- Assemble the code to machine code
l_asm := pfcl_asm.assemble(
'; --- Caller ---
MOV 15, r0, -10 ; r0 = 5 (parameter)
LDA r1, fact ; r1 = address of fact
BRA r1, r5 ; CALL fact
COP r0, r2 ; r2 = return value
HLT

; --- Subroutine ---
fact:
COP r0, r3 ; r3 = n (preserve parameter)
MOV 15, r0, -14 ; r0 = 1 (result accumulator)

floop:
MUL r3, r0 ; r0 = r0 * r3 (result *= n)
MOV r3, r3, -1 ; r3 = r3 - 1 (decrement n; sets flag)
BRN floop ; if n != 0, continue

COP r0, r4 ; r4 = result
COP r5, r0 ; r0 = return address (from r5)
BRA r0, r5 ; RETURN'
);

-- Load the VM and run
pfcl_vm.init;
pfcl_vm.load(l_asm);
pfcl_vm.run;

-- Read the results from r4
l_result := pfcl_vm.get_reg(5);
dbms_output.put_line('r4 = ' || l_result);
dbms_output.put_line('clock = ' || pfcl_vm.get_clock);
end;
/

The results of running the program are here:

SQL> @test_fact_5
r4 = 120
clock = 48

PL/SQL procedure successfully completed.

SQL>

This works and show the correct value of 120 for a factorial of 5. It used a sub-program to do the calculation. 5! is 5*4*3*2*1 = 120. This means we can run programs written in Assembler in PL/SQL.

The next demo is another common demo. We will calculate the 10th Fibonacci number which should be 55 when we start the sequence at 1. The assembly language program is:

'; === main ===
MOV 15, r0, -5 ; r0 = 10 (Fibonacci parameter)
MOV 15, sp, 285 ; sp = 300 (past program end at 272)
LDA r1, fib ; r1 = address of fib
BRA r1, r5 ; CALL fib (r5 = return address)
COP r0, r2 ; r2 = fib(10) = 55
LDA r1, print ; r1 = address of print
BRA r1, r5 ; CALL print (r5 = return address)
HLT

; === fib(n): in r0, out r0 ===
fib:
STO r5, sp, 0 ; mem[sp] = return address
MOV sp, sp, 1 ; sp++
STO r0, sp, 0 ; mem[sp] = n
MOV sp, sp, 1 ; sp++
LDA r1, fib ; r1 = address of fib
STO r1, sp, 0 ; mem[sp] = address of fib
MOV sp, sp, 1 ; sp++

; Base case: n = 0
MOV r0, r0, 0 ; zero flag if n == 0
BRZ ret_zero

; Base case: n = 1
MOV r0, r0, -1 ; zero flag if n == 1
BRZ ret_one

; Recursive: fib(n) = fib(n-1) + fib(n-2)
; r0 = n-1 (from check above)

LDO sp, r1, -1 ; r1 = address of fib
BRA r1, r5 ; CALL fib(n-1)
STO r0, sp, 0 ; push fib(n-1)
MOV sp, sp, 1 ; sp++

LDO sp, r1, -3 ; r1 = n
MOV r1, r0, -2 ; r0 = n-2
LDO sp, r2, -2 ; r2 = address of fib
BRA r2, r5 ; CALL fib(n-2)
LDO sp, r1, -1 ; r1 = fib(n-1)
ADD r1, r0 ; r0 = fib(n-2) + fib(n-1)

MOV sp, sp, -4 ; pop 4
LDO sp, r1, 0 ; r1 = return address
BRA r1, r5 ; RETURN

ret_zero:
MOV sp, sp, -3
LDO sp, r1, 0
BRA r1, r5 ; RETURN

ret_one:
MOV 15, r0, -14 ; r0 = 1
MOV sp, sp, -3
LDO sp, r1, 0
BRA r1, r5 ; RETURN

; === print(n): in r0, prints decimal 0-999 ===
print:
COP r0, r3 ; r3 = N
MOV r3, r3, 0 ; zero flag if N == 0
BRZ print_zero

; Hundreds digit
COP r3, r4 ; r4 = N
COP 100, r6 ; r6 = 100 (100 > 14, safe as immediate)
DIV r6, r4 ; r4 = trunc(N / 100)
MOV r4, r4, 0 ; zero flag
BRZ no_hun

COP r4, r1 ; r1 = digit
MOV r1, r1, 48 ; r1 = ASCII
COP r1, ro ; ro = code
OUT
COP r4, r1 ; r1 = digit
COP 100, r2 ; r2 = 100
MUL r2, r1 ; r1 = digit * 100
COP r3, r2 ; r2 = N
SUB r1, r2 ; r2 = N - digit*100
COP r2, r3 ; r3 = remainder

no_hun:
; Tens digit
COP r3, r4 ; r4 = N
MOV 15, r6, -5 ; r6 = 10 (15 + -5 = 10)
DIV r6, r4 ; r4 = trunc(N / 10)
MOV r4, r4, 0 ; zero flag
BRZ no_ten

COP r4, r1 ; r1 = digit
MOV r1, r1, 48 ; r1 = ASCII
COP r1, ro ; ro = code
OUT
COP r4, r1 ; r1 = digit
MOV 15, r2, -5 ; r2 = 10 (15 + -5 = 10)
MUL r2, r1 ; r1 = digit * 10
COP r3, r2 ; r2 = N
SUB r1, r2 ; r2 = N - digit*10
COP r2, r3 ; r3 = remainder

no_ten:
; Ones digit
COP r3, r4 ; r4 = ones digit
COP r4, r1 ; r1 = digit
MOV r1, r1, 48 ; r1 = ASCII
COP r1, ro ; ro = code
OUT

COP r5, r1 ; r1 = return address
BRA r1, r5 ; RETURN

print_zero:
MOV 15, r1, 33 ; r1 = 48 (ASCII zero)
COP r1, ro
OUT
COP r5, r1 ; r1 = return address
BRA r1, r5 ; RETURN'

And inserting this in the test harness is as follows:

-- test_fib.sql
-- Pete Finnigan
-- 15.09.2026
-- Test PFCL_ASM with recursive Fibonacci(10) = 55
-- Result is printed to output buffer via OUT instruction

declare
l_asm varchar2(32767);
l_out varchar2(32767);
l_result number;
begin
l_asm := pfcl_asm.assemble(
'; === main ===
MOV 15, r0, -5 ; r0 = 10 (Fibonacci parameter)
MOV 15, sp, 285 ; sp = 300 (past program end at 272)
LDA r1, fib ; r1 = address of fib
BRA r1, r5 ; CALL fib (r5 = return address)
COP r0, r2 ; r2 = fib(10) = 55
LDA r1, print ; r1 = address of print
BRA r1, r5 ; CALL print (r5 = return address)
HLT

; === fib(n): in r0, out r0 ===
fib:
STO r5, sp, 0 ; mem[sp] = return address
MOV sp, sp, 1 ; sp++
STO r0, sp, 0 ; mem[sp] = n
MOV sp, sp, 1 ; sp++
LDA r1, fib ; r1 = address of fib
STO r1, sp, 0 ; mem[sp] = address of fib
MOV sp, sp, 1 ; sp++

; Base case: n = 0
MOV r0, r0, 0 ; zero flag if n == 0
BRZ ret_zero

; Base case: n = 1
MOV r0, r0, -1 ; zero flag if n == 1
BRZ ret_one

; Recursive: fib(n) = fib(n-1) + fib(n-2)
; r0 = n-1 (from check above)

LDO sp, r1, -1 ; r1 = address of fib
BRA r1, r5 ; CALL fib(n-1)
STO r0, sp, 0 ; push fib(n-1)
MOV sp, sp, 1 ; sp++

LDO sp, r1, -3 ; r1 = n
MOV r1, r0, -2 ; r0 = n-2
LDO sp, r2, -2 ; r2 = address of fib
BRA r2, r5 ; CALL fib(n-2)
LDO sp, r1, -1 ; r1 = fib(n-1)
ADD r1, r0 ; r0 = fib(n-2) + fib(n-1)

MOV sp, sp, -4 ; pop 4
LDO sp, r1, 0 ; r1 = return address
BRA r1, r5 ; RETURN

ret_zero:
MOV sp, sp, -3
LDO sp, r1, 0
BRA r1, r5 ; RETURN

ret_one:
MOV 15, r0, -14 ; r0 = 1
MOV sp, sp, -3
LDO sp, r1, 0
BRA r1, r5 ; RETURN

; === print(n): in r0, prints decimal 0-999 ===
print:
COP r0, r3 ; r3 = N
MOV r3, r3, 0 ; zero flag if N == 0
BRZ print_zero

; Hundreds digit
COP r3, r4 ; r4 = N
COP 100, r6 ; r6 = 100 (100 > 14, safe as immediate)
DIV r6, r4 ; r4 = trunc(N / 100)
MOV r4, r4, 0 ; zero flag
BRZ no_hun

COP r4, r1 ; r1 = digit
MOV r1, r1, 48 ; r1 = ASCII
COP r1, ro ; ro = code
OUT
COP r4, r1 ; r1 = digit
COP 100, r2 ; r2 = 100
MUL r2, r1 ; r1 = digit * 100
COP r3, r2 ; r2 = N
SUB r1, r2 ; r2 = N - digit*100
COP r2, r3 ; r3 = remainder

no_hun:
; Tens digit
COP r3, r4 ; r4 = N
MOV 15, r6, -5 ; r6 = 10 (15 + -5 = 10)
DIV r6, r4 ; r4 = trunc(N / 10)
MOV r4, r4, 0 ; zero flag
BRZ no_ten

COP r4, r1 ; r1 = digit
MOV r1, r1, 48 ; r1 = ASCII
COP r1, ro ; ro = code
OUT
COP r4, r1 ; r1 = digit
MOV 15, r2, -5 ; r2 = 10 (15 + -5 = 10)
MUL r2, r1 ; r1 = digit * 10
COP r3, r2 ; r2 = N
SUB r1, r2 ; r2 = N - digit*10
COP r2, r3 ; r3 = remainder

no_ten:
; Ones digit
COP r3, r4 ; r4 = ones digit
COP r4, r1 ; r1 = digit
MOV r1, r1, 48 ; r1 = ASCII
COP r1, ro ; ro = code
OUT

COP r5, r1 ; r1 = return address
BRA r1, r5 ; RETURN

print_zero:
MOV 15, r1, 33 ; r1 = 48 (ASCII zero)
COP r1, ro
OUT
COP r5, r1 ; r1 = return address
BRA r1, r5 ; RETURN'
);

-- Load and run
pfcl_vm.init;
pfcl_vm.load(l_asm);
pfcl_vm.run;

-- Retrieve results
l_out := pfcl_vm.get_output;
dbms_output.put_line('output = [' || l_out || ']'); -- out=fib(10)
dbms_output.put_line('clock = ' || pfcl_vm.get_clock);
end;
/

When I run this we get:

SQL> @test_fib
output = [55]
clock = 6003

PL/SQL procedure successfully completed.

SQL>

This works correctly and show the tenth Fibonacci number.

The assembly language code is assembled via a PL/SQL assembler to binary machine code and then executed in a Virtual Machine (VM) written also in PL/SQL. This is just a simple test to show the system working. I will post more detailed blogs/articles over the coming months of the design and build of the VM and assembler as well as test suites to check that they both work and also a complete set of articles showing how i designed and created a compiler written in PL/SQL for a simple language that is compiled into assembly language for the VM presented today.

I also have around 150 pages of notes and articles about the interpreter started in 2022 / 2024. I have over 250 pages of articles and notes for each system combined (the VM, ASM, Compiler) and the Interpreter all written in PL/SQL. Keep an eye out, I will be releasing the series of articles as I have spent a huge amount of time and work on these. I will also release a short series showing how the compiler/VM/ASM or Interpreter can be embedded in an existing PL/SQL application so that it can be scripted at run time without exposing the ability to add dynamic PL/SQL to the end user.

I may release all three sets of articles as a complete PDF book if anyone is interested BUT after all articles are released individually.

#oracleace #oracleacepro #sym_42 #oracle #plsql #interpreter #compiler #asm #assembler #vm #virtualmachine #machinecode

Oracle Security AnythingLLM Tools

As you will have noticed in my blogs I have been using and playing with AI and Large Language Models (LLMs) for a while now but with a focus on Oracle Security still.

I want to understand the capabilities of running AI LLMs locally and do they offer value to my day job of Oracle security consulting, training and products and more generally can Oracle people; DBAs, Developers, Security people find value as well.

The key drivers for me are AI Sovereignty and protection of data. For years I have helped people protect data in Oracle databases and it seems AI has come along to drop us back 15 to 20 years; right to the beginning. People seem to be blind-sided by AI and think it is OK to open up the complete database schema to AI and transmit data in the form of prompts to AI providers.

I have spent years helping people lock down data using the controls within the core database and also helping implement additional cost and non-cost options such Database Vault, TDE, TSDP, audit trails, masking and basically everything possible including of course writing custom security code for Oracle databases.

One major gap in LLMs is the audit trails; yes there are some but they are not good enough. Any company serious about securing data in an Oracle should lock down that data, audit that data, control access to that data and have controls to provide logs of who accessed the data. In terms of LLMs we need to know who, when, why and what data was accessed (sent in a prompt) and what data was retrieved (response).

We should consider the design if we allow data from the database to be sent to an Large Language Model. We should limit or anonymise what data is sent and we should control the access paths to the data and ideally air gap it from production; in simple terms do not allow unfettered access to production with an LLM or agents or harnesses or...

In terms of whether an LLM is useful for day to day Oracle work; yes, it can be BUT it should not be a replacement for a DBA or developer. In other words don't think that you can reduce DBA roles and replace with an AI and use a harness, loop, agents and expect your database to work and be fully supported. In my experience so far of researching AI and using AI I have found that it may be great at vide coding or the new phrase One Shotting a web app or web based game or website BUT it seems to struggle with other areas. The main LLMs seem to be good at Javascript and HTML and CSS but less so with the details of Oracle databases or coding with PL/SQL or ...

There are clear gaps in using an LLM with Oracle; my guess would be because the main models have not been trained as well on the internals of Oracle and the documentation and there are also a lot less posts on various sources on the internet that focus on some Oracle tasks.

I think LLMs and particularly the open weight free ones are getting better and we can improve the use of LLMs with better more concise system prompts, tune the settings such as reasoning and temperature and context size. We can also teach open free weight models using tools such as Unsloth and we can of course use harnesses such as Pi or DeepSeek harenss to create agentic loops or graphs. There is a lot of power available with Local LLMs but it is clear they are getting better all of the time and it is clear that we need to put in a lot of effort in terms of inputs (RAG or training LoRA adaptors) to make them realy useful.

In simple terms the model needs to be set up and tuned and controlled and the right inputs need to be made available in terms of documentation fed into it via RAG or training and of course to use models successfully you need to be able to write prompts that work and be able to already know if the answer is correct or sounding correct. This is why I think the use of AI / LLMs is useful as an expert assistant and not a replacement.

MOST IMPORTANT; before you consider using either frontier models or set up your local LLM you need to consider auditing and data exposure as I said above BUT if you spend time on AI how does it provide ROI (Return On Investment) for you. There are mainly two ways; increase productivity to make more money by doing more in the same time or reduce costs. So, paying huge amounts for tokens that do not generate more turnover or revenue does not work. Use of AI should increase net profit otherwise what is the point.

I have been using a local AI set up with a Macbook Pro M5 MAX with 64gb of RAM for AI and normal use. This means I can run local models around 30 billion parameters easily and with good performance for one person (me) or larger models around 70B in some cases with reduced performance. I am using ollama to run the models and I was using OpenWebui to provide a chat interface but I wanted to move to AnythingLLM as a better model control interface and also to have some tools available OOTB (Out Of The Box). I am running the mac as a server and access OpenWebui and AnythingLLM from a Windows laptop via the browser.

Setting up AnythingLLM was easy. I had to download another model for the embedding nomic-embed-text. I also then had to pull anythingllm via docker on the mac and then use a docker command to run anythingllm on port 3001 within docker; I used port 3000 for OpenWebUI already. Once started I can then access the LLMs via AnythingLLM from a browser on my Windows laptop.


The real focus of AnythingLLM is the tools interface and the fact that you can upload documents and source code to it and these are indexed and stored in a vector database. The idea is that you can create different workspaces and different documents to use in the interface either by chat or by agents.

A first test i did was to ask Quen3.6:27b via AnythingLLM What is the latest version of the Oracle database for Windows?

The answer came back via AnythingLLM as:

Based on my latest training data, Oracle Database 23ai (version 23.0) is the newest major release, with 19c remaining the current Long-Term Support (LTS) version. Both are officially supported on Windows x64. Oracle frequently rolls out quarterly patches and minor updates, so for the exact latest build number, download links, and compatibility notes, please visit:

Official downloads: https://www.oracle.com/database/technologies/oracle-database-software-downloads.html
Patch/Release Notes: My Oracle Support (MOS) or OTN’s Release History page
If you share your Windows version and use case (development vs. production), I can help you choose the right edition, verify system requirements, or walk through installation/upgrading steps.

Hmmm, obviously wrong BUT it explained why it is wrong; at the time the model was trained Oracle 23ai was the latest version.

So, using AnythingLLM I can improve this result by enabling the web search in the current workspace and enable DuckDuckGo search which is built in and send the same prompt again.

This time I get this answer:

Based on the latest search results, the newest major version is Oracle AI Database 26ai (often referred to simply as Oracle Database 26).

This was officially released in early 2026 and serves as the new Long-Term Release (LTR), replacing Oracle Database 19c as the primary long-term support option for production environments. It is fully supported on Windows x64 and includes official installation guides for Windows (ntcli).

Key details:

Latest Major Version: Oracle AI Database 26ai (version 26.0+)
Release Type: Long-Term Release (LTS)
Windows Support: Yes, fully supported on Windows x64 platforms.
Predecessors: It succeeds Oracle Database 19c (previous LTS) and Oracle Database 23ai (which served as an interim release with a shorter support window).
You can download the latest installers for Windows directly from the Oracle AI Database Software Downloads page.

So clearly we can combine the models own weights (knowledge) with RAG (documentation) and search (DuckDuckGo) and even teach a model with data and unsloth.

I will be testing more with AnythingLLM in respect to my work with Oracle and security and also newer models as they become available and update you here.

#oracleace #oracleacepro #sym_42 #oracle #database #security #llm #ai #anythingllm #unsloth #rag #duckduckgo #qwen

Perform a Security Audit of an Old Oracle Database

We support doing security audits of all of the current Oracle databases from 19c to 21c and 26ai. We also support doing security audits on databases in your own data center or in the cloud. No matter where the database is we can audit it.

We get requests occasionally for old databases to be audited and had one such request to be able to audit a 9iR2 database. The customer wanted to use our scanner PFCLScan to do this but wanted a small footprint on the clients Windows PC where PFCLScan runs from.

We have a tool called OEMFrame.exe that was used for a previous collaboration with an Oracle tools vendor. This is a very cut down version of the complete scanner and much smaller and command line scans only BUT you can choose the OCI library needed (Oracle Call Interface not Cloud) and choose the report output type such as HTML, JSON, XML etc.

We can extract the command line tool from a complete scanner install and it then needs to be deployed simply as a zip file. Once deployed the customer needs to run a set up tool from the command line and then request a license key also from the command line and finally once we supply the license key locked to the installation it needs to be applied via a command line tool. Simple, quick and easy and command line only BUT it is a full scan of the database.

Running a scan is simple and is one command:

C:\>cd customers\xxx_xxxxx\pfclscan\PFCLScan_Bin\bin

C:\customers\xxx_xxxxx\pfclscan\PFCLScan_Bin\bin>pfclset
pfclset.bat Release 1.0 Copyright 2015 PeteFinnigan.com Limited

c:\customers\xxx_xxxxx\pfclscan\PFCLScan_Data>oemframe system oracle1 192.168.1.36 1521 orcl.localdomain
[2026 Sep 09 10:16:11] OEMFrame : Opening the application settings file

OEMFrame: Release 6.0.26.1506 - Production on Wed, 09 Sep 2026 10:16:11 GMT

Copyright (c) 2026 PeteFinnigan.com Limited. All rights reserved.

[2026 Sep 09 10:16:11] OEMFrame : Starting OEMFrame...
[2026 Sep 09 10:16:11] OEMFrame : Create Credentials
[2026 Sep 09 10:16:11] OEMFrame : Run oemrun


Press any key to exit.

oemrun.bat Release 1.0 Copyright 2015 PeteFinnigan.com Limited, Production on 09/09/2026 10:16:11.78

[09/09/2026 10:16:11.79] oemrun: Start running OEM project processor
[09/09/2026 10:16:11.79] oemrun: Copy safe oemscan project
[09/09/2026 10:16:11.79] oemrun: Run the OEMBUILD project
...

The username and password are passed in clear text here as they would be using SQL*Plus BUT they can be encrypted first and during all steps of the scan the username and password are encrypted even if passed in clear text. This is just a demo here to show the functionality.

The scan runs the full suite of thousands of security checks that the normal GUI version of PFCLScan runs.

A sample of a policy being executed and scanned is here:

...
[2026 Sep 09 10:16:13] Lock : Creating policy file=[c:\customers\xxx_xxxxx\pfclscan\PFCLScan_Data\PeteFinnigan.com Limited\PFCLScan\plugins\policy\policy.4.1.29.1.0.conf.xml]
[2026 Sep 09 10:16:13] Lock : Closing Down LOCK
LOAD: Release 6.0.26.1506 - Production on Wed, 09 Sep 2026 10:16:13 GMT
Copyright (c) 2026 PeteFinnigan.com Limited. All rights reserved.
[2026 Sep 09 10:16:13] Load : Starting LOAD...
[2026 Sep 09 10:16:13] Load : Opening the application settings file
[2026 Sep 09 10:16:13] Load : Opening the project: c:\customers\xxx_xxxxx\pfclscan\PFCLScan_Data\PeteFinnigan.com Limited\PFCLScan\plugins\oemscan.pfclx
[2026 Sep 09 10:16:13] Load : Run Number=[cur]
[2026 Sep 09 10:16:13] Load : run cmd line [oscan -c c:\customers\xxx_xxxxx\pfclscan\PFCLScan_Data\PeteFinnigan.com Limited\PFCLScan\plugins\policy\4.1.1.1.0.conf -v]
OSCAN: Release 6.0.12.1526 - Production on Wed Sep 9 10:16:13 2026
Copyright (c) 2026 PeteFinnigan.com Limited. All rights reserved.
[2026 Sep 09 09:16:13] Oscan: Starting OSCAN...
[2026 Sep 09 09:16:13] Oscan: Running Scanner
[2026 Sep 09 09:16:13] Oscan: Load Test from XML...
[2026 Sep 09 09:16:13] Oscan: Load policy from XML...
[2026 Sep 09 09:16:13] Oscan: Load dictionary file...
[2026 Sep 09 09:16:13] Oscan: Load default list file...
[2026 Sep 09 09:16:13] Oscan: Connect to the database....
[2026 Sep 09 09:16:13] Oscan: Server Attached to [//192.168.1.36:1521/orcl.localdomain]
[2026 Sep 09 09:16:13] Oscan: Connected to [//192.168.1.36:1521/orcl.localdomain] as [:E:FE21B3993FCA2E83]
[2026 Sep 09 09:16:13] Oscan: Opening Output File
[2026 Sep 09 09:16:13] Oscan: [-] Stabalisation Check
[2026 Sep 09 09:16:13] Oscan: [-] Audit Users Privileges
[2026 Sep 09 09:16:13] Oscan: Disconnecting from [//192.168.1.36:1521/orcl.localdomain] as [:E:FE21B3993FCA2E83]
[2026 Sep 09 09:16:13] Oscan: Closing Output File [oscan.op.4.1.1.xml]
[2026 Sep 09 09:16:13] Oscan: Closing Down OSCAN
[2026 Sep 09 10:16:13] Load : Exit code=[0]
[2026 Sep 09 10:16:13] Load : Completed [oscan -c "c:\customers\xxx_xxxxx\pfclscan\PFCLScan_Data\PeteFinnigan.com Limited\PFCLScan\plugins\policy\4.1.1.1.0.conf" -v]
[2026 Sep 09 10:16:13] Load : Update the project runset
[2026 Sep 09 10:16:13] Load : Testing run file [c:\customers\xxx_xxxxx\pfclscan\PFCLScan_Data\PeteFinnigan.com Limited\PFCLScan\reports\run.4.1.1.data.xml] exists
[2026 Sep 09 10:16:13] Load : Testing raw file [c:\customers\xxx_xxxxx\pfclscan\PFCLScan_Data\oscan.op.4.1.1.xml] exists
[2026 Sep 09 10:16:13] Load : Running Loop compactor
[2026 Sep 09 10:16:13] Load : Run Loopcompact [loop "c:\customers\xxx_xxxxx\pfclscan\PFCLScan_Data\oscan.op.4.1.1.xml"]
...

Here is part of the HTML report generated:
PFCLScan command line scanner on 9iR2



We were asked by a customer to scan a 9.2.0.8 database using PFCLScan so we used our cut down scanner above and tested it locally here on an old 9.2.0.1 database that we had an old virtual Box VM of. This VM had not been started for just over 10 years but worked.

Because we do not scan 9iR2 or indeed 10gR2 or 11gR2 anymore in testing and we update the scanner checks on a regular basis we found a few small issues where we use more modern techniques to do things now that do not work in 9iR2. We fixed these in a customer specific download and now have a working version of the scanner that will scan 9iR2. 11gR2 should work as we tested 11gR2 much more recently and 10gR2 can be made to work easily or may work now if we test it

Why scan old databases?

Some customers will be forced to run old out of date Oracle databases in some cases. The usual reason is they still have customers on old systems that will age out and then the system will be decommissioned when all the customers have had the service completed. Some run old applications where the vendor is not available and they do not want to run on newer databases. There are systems around still that use older databases.

Whilst there are not any security patches available for 9iR2 (of course) there is still a lot of the database configuration and controls that can be changed and tightened to improve the security even of old databases.

#oracleace #oracleacepro #sym_42 #oracle #database #security #9ir2 #scanning #pfclscan

Can Synonyms Point to Synonyms?

I got asked a question via a DM on one of my social media channels a few days ago and thought it worth an investigation. They asked me; Can an Oracle synonym point to another synonym, ad infinitum.

Let me do a test and see. First what privileges exist that involve synonyms:

SQL> select name from system_privilege_map where name like '%SYNONYM%';

NAME
----------------------------------------
DROP PUBLIC SYNONYM
CREATE PUBLIC SYNONYM
DROP ANY SYNONYM
CREATE ANY SYNONYM
CREATE SYNONYM

SQL>

There are two groups of synonym privileges; drop and create PUBLIC synonyms and drop and create ANY and one single privilege CREATE SYNONYM to create a single synonym in a schema. For the single create of synonym in your own schema you do not need a DROP because of the OBJECT OWNER PRINCIPAL as the owner of an object once its created does not need privileges to change their own objects.

Let me create a user and then add permissions and connect to the user:

SQL> create user syntest identified by syntest;

User created.

SQL> grant create session, create synonym to syntest;

Grant succeeded.

SQL> connect syntest/syntest@//192.168.56.33:1539/xepdb1
Connected.
SQL>

Now as this user create a synonym:

SQL> create synonym all_users for sys.all_users;

Synonym created.

SQL>

Now try and create another synonym pointing to this synonym and just for fun create another synonym to that synonym so that we have synonym to synonym to sys.all_users

SQL> create synonym all_users1 for all_users;

Synonym created.

SQL> create synonym all_users2 for all_users1;

Synonym created.

SQL>

That works. What does the meta data show:

SQL> set serveroutput on
SQL> @sc_print 'select * from dba_synonyms where synonym_name like ''''ALL_USERS%'''''
old 32: lv_str:=translate('&&1','''','''''');
new 32: lv_str:=translate('select * from dba_synonyms where synonym_name like ''ALL_USERS%''','''','''''');
Executing Query [select * from dba_synonyms where synonym_name like
'ALL_USERS%']
OWNER : PUBLIC
SYNONYM_NAME : ALL_USERS
TABLE_OWNER : SYS
TABLE_NAME : ALL_USERS
DB_LINK :
ORIGIN_CON_ID : 1
-------------------------------------------
OWNER : SYNTEST
SYNONYM_NAME : ALL_USERS
TABLE_OWNER : SYS
TABLE_NAME : ALL_USERS
DB_LINK :
ORIGIN_CON_ID : 3
-------------------------------------------
OWNER : SYNTEST
SYNONYM_NAME : ALL_USERS1
TABLE_OWNER : SYNTEST
TABLE_NAME : ALL_USERS
DB_LINK :
ORIGIN_CON_ID : 3
-------------------------------------------
OWNER : SYNTEST
SYNONYM_NAME : ALL_USERS2
TABLE_OWNER : SYNTEST
TABLE_NAME : ALL_USERS1
DB_LINK :
ORIGIN_CON_ID : 3
-------------------------------------------

PL/SQL procedure successfully completed.

SQL>

So, yes synonyms can point at synonyms ad-infinitum. We can also use these synonyms and they work:

SQL> connect syntest/syntest@//192.168.56.33:1539/xepdb1
Connected.
SQL> select count(*) from all_users2;

COUNT(*)
----------
87

SQL> select count(*) from all_users1;

COUNT(*)
----------
87

SQL> select count(*) from all_users;

COUNT(*)
----------
87

SQL> select count(*) from sys.all_users;

COUNT(*)
----------
87

SQL>

Hmm, this is a confusing situation to be in if you have a chain of synonyms pointing eventually to an object. From a security perspective this would be hard to understand and to ensure everything was correct.

#oracleace #oracleacepro #sym_42 #oracle #database #security #synonyms

Contexts are Database Level Objects in Oracle

I was asked by someone recently why their context values had disappeared. They had two database users that created the same context. Yes, I know you cannot do that but there was a subtle reason that the code did not fail (DROP then CREATE in their build scripts). But the attribute/values for the first user disappeared.

The reason was simple; the context is a database level object and does not have any owner and is not associated to one scheme.

The second issue (also obvious) is that a DROP USER... CASCADE... does not remove a context, simply because it is not associated with a specific user so it is not dropped.

Let me create a simple example. First create a user and a package to control the context:

SQL> create user context1 identified by context1;

User created.

SQL> grant create session, create any context, create procedure to context1;

Grant succeeded.

SQL>

Note that we must grant CREATE ANY CONTEXT as there is no CREATE CONTEXT system privilege because contexts are global? Next connect as the context1 user and create the simple package that will be associated with the context:

SQL> connect context1/context1@//192.168.56.33:1539/xepdb1
Connected.
SQL> create or replace package contextset as
2 procedure set_user(
3 pv_username varchar2
4 );
5 end;
6 /

Package created.

SQL>
SQL> create or replace package body contextset as
2
3 procedure set_user(
4 pv_username varchar2
5 ) is
6 begin
7 dbms_session.set_context(
8 namespace => 'appcontext',
9 attribute => 'username',
10 value => pv_username
11 );
12 end;
13
14 end;
15 /

Package body created.

SQL>

Now we can create context for this user:

SQL> create context appcontext using contextset;

Context created.

SQL>

Now, finally we can set the context and retrieve it:

SQL> exec contextset.set_user('CONTEXT1');

PL/SQL procedure successfully completed.

SQL> select sys_context('appcontext','username') from dual;

SYS_CONTEXT('APPCONTEXT','USERNAME')
--------------------------------------------------------------------------------
CONTEXT1

SQL>

This works as expected. What if we then create a user CONTEXT2 and create the same context using CONTEXT2 version of the package:

SQL> create user context2 identified by context2;

User created.

SQL> grant create session, create any context, create procedure to context2;

Grant succeeded.

SQL>

Connect to context2 and create the context2 version of the package for the context:

SQL> connect context2/context2@//192.168.56.33:1539/xepdb1
Connected.
SQL> create or replace package contextset as
2 procedure set_user(
3 pv_username varchar2
4 );
5 end;
6 /

Package created.

SQL>
SQL> create or replace package body contextset as
2
3 procedure set_user(
4 pv_username varchar2
5 ) is
6 begin
7 dbms_session.set_context(
8 namespace => 'appcontext',
9 attribute => 'username',
10 value => pv_username
11 );
12 end;
13
14 end;
15 /

Package body created.

SQL>

Create the context and set and read it:

SQL> create context appcontext using contextset;
create context appcontext using contextset
*
ERROR at line 1:
ORA-00955: name is already used by an existing object


SQL>

And if we try and set the context we get:

SQL> exec contextset.set_user('CONTEXT2');
BEGIN contextset.set_user('CONTEXT2'); END;

*
ERROR at line 1:
ORA-01031: insufficient privileges
ORA-06512: at "SYS.DBMS_SESSION", line 141
ORA-06512: at "CONTEXT2.CONTEXTSET", line 7
ORA-06512: at line 1


SQL>

A context is global in the database and we cannot create the same context per user. We have a number of possible solutions and which to use / choose depends on what is needed in the application.

The first is that each user can have their own contexts but they need unique names. The second is that we can create the context as one user and its global but then create the access package as one user and allow users to execute it and then when setting the context qualify the package and use it from the second user.

Connect back to the first user and grant execute to context2 and test again:

SQL> connect context1/context1@//192.168.56.33:1539/xepdb1
Connected.
SQL> grant execute on contextset to context2;

Grant succeeded.

SQL>

Connect back to Context2 and try again to set it:

SQL> connect context2/context2@//192.168.56.33:1539/xepdb1
Connected.
SQL> exec context1.contextset.set_user('CONTEXT2');

PL/SQL procedure successfully completed.

SQL> select sys_context('appcontext','username') from dual;

SYS_CONTEXT('APPCONTEXT','USERNAME')
--------------------------------------------------------------------------------
CONTEXT2

SQL>

It now works for both users. The key is to understand that a context is at the database level and not the schema level. Each user/schema can now log in and use the same context to store their username in that context. I have created two command windows and these are here showing that it works:

SQL> -- context1
SQL> connect context1/context1@//192.168.56.33:1539/xepdb1
Connected.
SQL> select sys_context('appcontext','username') from dual;

SYS_CONTEXT('APPCONTEXT','USERNAME')
--------------------------------------------------------------------------------


SQL> exec context1.contextset.set_user('CONTEXT1');

PL/SQL procedure successfully completed.

SQL> select sys_context('appcontext','username') from dual;

SYS_CONTEXT('APPCONTEXT','USERNAME')
--------------------------------------------------------------------------------
CONTEXT1

SQL> --context2
C:\Users\pete>sqlplus context2/context2@//192.168.56.33:1539/xepdb1

SQL*Plus: Release 19.0.0.0.0 - Production on Wed Sep 2 09:58:57 2026
Version 19.28.0.0.0

Copyright (c) 1982, 2025, Oracle. All rights reserved.

Last Successful login time: Wed Sep 02 2026 09:22:28 +01:00

Connected to:
Oracle Database 21c Express Edition Release 21.0.0.0.0 - Production
Version 21.3.0.0.0

SQL> exec context1.contextset.set_user('CONTEXT2');

PL/SQL procedure successfully completed.

SQL> select sys_context('appcontext','username') from dual;

SYS_CONTEXT('APPCONTEXT','USERNAME')
--------------------------------------------------------------------------------
CONTEXT2

SQL>

Let us see whats stored in the meta data for the context:

SQL> @sc_print 'select * from dba_context where schema like ''''%CONTEXT%'''''
old 32: lv_str:=translate('&&1','''','''''');
new 32: lv_str:=translate('select * from dba_context where schema like ''%CONTEXT%''','''','''''');
Executing Query [select * from dba_context where schema like '%CONTEXT%']
NAMESPACE : APPCONTEXT
SCHEMA : CONTEXT1
PACKAGE : CONTEXTSET
TYPE : ACCESSED LOCALLY
ORIGIN_CON_ID : 3
TRACKING : YES
-------------------------------------------

PL/SQL procedure successfully completed.

SQL>

As we can see the context is not assigned to a specific user/schema. It does however specify the schema that owns the PL/SQL that allows the context to be set.

If we drop context1 cascade does the context get removed?

SQL> drop user context1 cascade;

User dropped.

SQL> set serveroutput on
SQL> @sc_print 'select * from dba_context where schema like ''''%CONTEXT%'''''
old 32: lv_str:=translate('&&1','''','''''');
new 32: lv_str:=translate('select * from dba_context where schema like ''%CONTEXT%''','''','''''');
Executing Query [select * from dba_context where schema like '%CONTEXT%']
NAMESPACE : APPCONTEXT
SCHEMA : CONTEXT1
PACKAGE : CONTEXTSET
TYPE : ACCESSED LOCALLY
ORIGIN_CON_ID : 3
TRACKING : YES
-------------------------------------------

PL/SQL procedure successfully completed.

SQL>

No, the context is at the database level and not the schema level so even though CONTEXT1 created it, it is not removed when CONTEXT1 is dropped cascade. It will not work now as the package has gone.

We are interested in contexts as they are used extensively in Oracle security solutions such as VPD, RAS, Deep Security (new in 26ai) and our own solutions. we can see this by the quantity of contexts available in a default database:

SQL> set lines 220
SQL> col namespace for a30
SQL> col schema for a20
SQL> col package for a30
SQL> col type for a20
SQL> select namespace,schema,package,type from dba_context;

NAMESPACE SCHEMA PACKAGE TYPE
------------------------------ -------------------- ------------------------------ --------------------
LSBY_APPLY_CONTEXT SYS DBMS_LOGSTDBY_CONTEXT ACCESSED LOCALLY
GLOBAL_AQCLNTDB_CTX SYS DBMS_AQJMS ACCESSED GLOBALLY
DBFS_CONTEXT SYS DBMS_DBFS_CONTENT_ADMIN ACCESSED GLOBALLY
REGISTRY$CTX SYS DBMS_REGISTRY_SYS ACCESSED LOCALLY
SHARD_CTX GSMADMIN_INTERNAL DBMS_GSM_POOLADMIN ACCESSED LOCALLY
SHARD_CTX2 GSMADMIN_INTERNAL DBMS_GSM_UTILITY ACCESSED LOCALLY
LT_CTX WMSYS LT_CTX_PKG ACCESSED LOCALLY
DR$APPCTX CTXSYS DRIXMD ACCESSED LOCALLY
SDO_SEM_HTTP_CTX MDSYS SDO_SEM_HTTP_CTX ACCESSED LOCALLY
SDO_SEM_CTX MDSYS SDO_SEM_CTX ACCESSED GLOBALLY
SDO_SEM_CTX_SESSION MDSYS SDO_SEM_CTX_SESSION ACCESSED LOCALLY

NAMESPACE SCHEMA PACKAGE TYPE
------------------------------ -------------------- ------------------------------ --------------------
SDO_SEM_UPDATE_CTX MDSYS SDO_SEM_UPDATE_CTX ACCESSED LOCALLY
OPG_CTX MDSYS OPG_CTX ACCESSED GLOBALLY
OPG_CTX_SESSION MDSYS OPG_CTX_SESSION ACCESSED LOCALLY
LBAC_CTX LBACSYS LBAC_CACHE ACCESSED LOCALLY
LBAC$LABELS LBACSYS LBAC_CACHE ACCESSED LOCALLY
ORA_OLS_SESSION_LABELS LBACSYS SA_AUDIT_ADMIN ACCESSED LOCALLY
MAC$FACTOR DVSYS DBMS_MACSEC ACCESSED LOCALLY
FLAG_LOCK ORABLOG WRITE_LOG ACCESSED GLOBALLY
APPCONTEXT CONTEXT1 CONTEXTSET ACCESSED LOCALLY

20 rows selected.

SQL>


Be aware that contexts are global and the same one cannot be created by two or more users/schemas and also be aware that they need to be dropped separately

#oracleace #oracleacepro #sym_42 #oracle #database #security #context

Using Qwen3.8:27b to create a PL/SQL encrypt/decrypt Package

A few weeks ago I did a sample blog post to use local LLM to create a PL/SQL package to encrypt and decrypt data in an Oracle database - AI Comparison for Oracle Security Code Generation.

In that blog I used qwen2.5-coder, qwen3-coder and qwen3-coder-next - in summary; qwen3-coder-next ran for 51 minutes and went into a loop and hung; the other two generated code but both hallucinated Oracle package names and procedures. Qwen2.5 was a third as fast as qwen3-coder but neither created code that would compile.

Whilst I could have fixed the code and made it work that was not the focus of that particular test. The local LLMs can be used as an assistant to help progress rapid development but only if you know what you are doing. Yes, we could better use an agent and a loop and get it to compile the code and fix the bugs found and recompile until it works.

Qwen3.8:27b was released very recently so I updated ollama and downloaded the model (ollama pull qwen3.8:27b) and then ran the model through AnythingLLM via its chat prompt where ollama is running on my macbook pro M5 max 64gb laptop. I decided with this much heralded new model to test the same PL/SQL encrypt / decrypt package generation.

I ran this prompt:

please write a pl/sql package to include an encrypt and decrypt function and also private base64 code and decode functions that should be used to base64 encrypted data and base64 decode encrypted data in the encrypt and decrypt functions


This ran really quickly in 2minutes 4 seconds and at 35.12 tokens per second. It created a package header:

CREATE OR REPLACE PACKAGE sec_crypto_pkg IS

-- Public: Encrypt plain text and return Base64-encoded ciphertext.
-- p_plain_text : The text to encrypt.
-- p_key : The encryption key (will be hashed to 32 bytes for AES-256).
-- p_iv : Optional initialization vector (16 bytes). Auto-generated if NULL.
-- Returns: Base64-encoded string of (IV || ciphertext || HMAC).
FUNCTION encrypt(
p_plain_text IN VARCHAR2,
p_key IN VARCHAR2,
p_iv IN VARCHAR2 DEFAULT NULL
) RETURN VARCHAR2;

-- Public: Decrypt a Base64-encoded ciphertext back to plain text.
-- p_cipher_text : Base64-encoded string produced by ENCRYPT.
-- p_key : The same key used during encryption.
-- Returns: The original plain text.
FUNCTION decrypt(
p_cipher_text IN VARCHAR2,
p_key IN VARCHAR2
) RETURN VARCHAR2;

END sec_crypto_pkg;
/

And a package body:

CREATE OR REPLACE PACKAGE BODY sec_crypto_pkg IS

--------------------------------------------------------------------------
-- CONSTANTS
--------------------------------------------------------------------------
c_aes256 CONSTANT PLS_INTEGER := DBMS_CRYPTO.ENCRYPT_AES256;
c_hmac_sha256 CONSTANT PLS_INTEGER := DBMS_CRYPTO.HMAC_SHA256;
c_iv_len CONSTANT PLS_INTEGER := 16; -- AES block size in bytes
c_hmac_len CONSTANT PLS_INTEGER := 32; -- SHA-256 output in bytes

--------------------------------------------------------------------------
-- PRIVATE: Base64 Encode
-- Converts a RAW value into its Base64 VARCHAR2 representation.
--------------------------------------------------------------------------
FUNCTION base64_encode(p_raw IN RAW) RETURN VARCHAR2 IS
l_result VARCHAR2(32767);
l_chunk VARCHAR2(32767);
l_offset PLS_INTEGER := 1;
l_chunk_len CONSTANT PLS_INTEGER := 4000; -- process in chunks to avoid limits
BEGIN
IF p_raw IS NULL OR UTL_RAW.LENGTH(p_raw) = 0 THEN
RETURN NULL;
END IF;

LOOP
l_chunk := UTL_RAW.SUBSTR(p_raw, l_offset, l_chunk_len);
l_result := l_result || UTL_RAW.RAWTOSTR(
UTL_I18N.STRINGTOHEX(UTL_RAW.CAST_TO_RAW(l_chunk))
);
-- Simpler and correct approach:
EXIT WHEN l_offset + l_chunk_len > UTL_RAW.LENGTH(p_raw);
l_offset := l_offset + l_chunk_len;
END LOOP;

-- The simplest correct approach for Base64 in Oracle:
RETURN (SELECT UTL_RAW.CAST_TO_VARCHAR2(
UTL_I18N.STRINGTOHEX(
-- We'll use a proper method below
)
) FROM DUAL);

END base64_encode;

--------------------------------------------------------------------------
-- PRIVATE: Base64 Decode
-- Converts a Base64 VARCHAR2 string back into a RAW value.
--------------------------------------------------------------------------
FUNCTION base64_decode(p_b64 IN VARCHAR2) RETURN RAW IS
BEGIN
IF p_b64 IS NULL THEN
RETURN NULL;
END IF;
RETURN NULL; -- placeholder, see corrected implementation below
END base64_decode;

--------------------------------------------------------------------------
-- PRIVATE: Derive a 32-byte key from an arbitrary passphrase.
--------------------------------------------------------------------------
FUNCTION derive_key(p_key IN VARCHAR2) RETURN RAW IS
BEGIN
-- Hash the key material to a fixed 32-byte (AES-256) key.
RETURN DBMS_CRYPTO.HASH(
UTL_I18N.STRINGTORAW(p_key, 'UTF8'),
c_hmac_sha256
);
END derive_key;

--------------------------------------------------------------------------
-- PUBLIC: ENCRYPT
--------------------------------------------------------------------------
FUNCTION encrypt(
p_plain_text IN VARCHAR2,
p_key IN VARCHAR2,
p_iv IN VARCHAR2 DEFAULT NULL
) RETURN VARCHAR2 IS
l_key RAW(32);
l_iv RAW(16);
l_plaintext RAW;
l_ciphertext RAW;
l_hmac RAW(32);
l_combined RAW(32767);
l_b64 VARCHAR2(32767);
BEGIN
IF p_plain_text IS NULL OR p_key IS NULL THEN
RETURN NULL;
END IF;

-- Derive fixed-length key
l_key := derive_key(p_key);

-- Use provided IV or generate a random one
IF p_iv IS NOT NULL THEN
l_iv := UTL_I18N.STRINGTORAW(p_iv, 'UTF8');
ELSE
l_iv := DBMS_CRYPTO.RANDOMBYTES(c_iv_len);
END IF;

-- Convert plain text to RAW
l_plaintext := UTL_I18N.STRINGTORAW(p_plain_text, 'UTF8');

-- Encrypt with AES-256
l_ciphertext := DBMS_CRYPTO.ENCRYPT(
src => l_plaintext,
typ => c_aes256,
key => l_key,
iv => l_iv
);

-- Compute HMAC over (IV || ciphertext) for integrity
l_combined := l_iv || l_ciphertext;
l_hmac := DBMS_CRYPTO MAC(
src => l_combined,
typ => c_hmac_sha256,
key => l_key
);

-- Final payload: IV (16) || ciphertext (variable) || HMAC (32)
l_combined := l_iv || l_ciphertext || l_hmac;

-- Base64 encode the binary payload
l_b64 := base64_encode(l_combined);

RETURN l_b64;

END encrypt;

--------------------------------------------------------------------------
-- PUBLIC: DECRYPT
--------------------------------------------------------------------------
FUNCTION decrypt(
p_cipher_text IN VARCHAR2,
p_key IN VARCHAR2
) RETURN VARCHAR2 IS
l_key RAW(32);
l_combined RAW;
l_iv RAW(16);
l_ciphertext RAW;
l_hmac_stored RAW(32);
l_hmac_calc RAW(32);
l_plaintext RAW;
BEGIN
IF p_cipher_text IS NULL OR p_key IS NULL THEN
RETURN NULL;
END IF;

l_key := derive_key(p_key);

-- Base64 decode the input
l_combined := base64_decode(p_cipher_text);

IF UTL_RAW.LENGTH(l_combined) < c_iv_len + c_hmac_len THEN
RAISE_APPLICATION_ERROR(-20001, 'Invalid or truncated ciphertext.');
END IF;

-- Parse: IV (16) || ciphertext (rest - 32) || HMAC (32)
l_iv := UTL_RAW.SUBSTR(l_combined, 1, c_iv_len);
l_hmac_stored := UTL_RAW.SUBSTR(l_combined, UTL_RAW.LENGTH(l_combined) - c_hmac_len + 1, c_hmac_len);
l_ciphertext := UTL_RAW.SUBSTR(l_combined, c_iv_len + 1,
UTL_RAW.LENGTH(l_combined) - c_iv_len - c_hmac_len);

-- Verify HMAC
l_hmac_calc := DBMS_CRYPTO.MAC(
src => l_iv || l_ciphertext,
typ => c_hmac_sha256,
key => l_key
);

IF l_hmac_calc <> l_hmac_stored THEN
RAISE_APPLICATION_ERROR(-20002, 'HMAC verification failed " data may be tampered.');
END IF;

-- Decrypt
l_plaintext := DBMS_CRYPTO.DECRYPT(
src => l_ciphertext,
typ => c_aes256,
key => l_key,
iv => l_iv
);

-- Convert RAW back to UTF-8 VARCHAR2
RETURN UTL_I18N.RAWTOHEX(l_plaintext); -- placeholder; see note below

END decrypt;

END sec_crypto_pkg;
/

And it also generated a completely standalone Base64 encode and decode to replace the placeholder above and the encoder using native code and no build in packages from Oracle. The only dependencies are DBMS_CRYPTO and UTL_I18N according to the final summary text. I have used the non-dependant PL/SQL base64 code and pasted that into the package body in the correct place.

To run this code we need database permissions on the packages and the ability to create PL/SQL so lets just cheat for this demo and install the code as SYS. The complete package with the custom base64 code is:

-- PL/SQL encrypt/decrypt package header
CREATE OR REPLACE PACKAGE sec_crypto_pkg IS

-- Public: Encrypt plain text and return Base64-encoded ciphertext.
-- p_plain_text : The text to encrypt.
-- p_key : The encryption key (will be hashed to 32 bytes for AES-256).
-- p_iv : Optional initialization vector (16 bytes). Auto-generated if NULL.
-- Returns: Base64-encoded string of (IV || ciphertext || HMAC).
FUNCTION encrypt(
p_plain_text IN VARCHAR2,
p_key IN VARCHAR2,
p_iv IN VARCHAR2 DEFAULT NULL
) RETURN VARCHAR2;

-- Public: Decrypt a Base64-encoded ciphertext back to plain text.
-- p_cipher_text : Base64-encoded string produced by ENCRYPT.
-- p_key : The same key used during encryption.
-- Returns: The original plain text.
FUNCTION decrypt(
p_cipher_text IN VARCHAR2,
p_key IN VARCHAR2
) RETURN VARCHAR2;

END sec_crypto_pkg;
/
sho err

-- PL/SQL package body
CREATE OR REPLACE PACKAGE BODY sec_crypto_pkg IS

--------------------------------------------------------------------------
-- CONSTANTS
--------------------------------------------------------------------------
c_aes256 CONSTANT PLS_INTEGER := DBMS_CRYPTO.ENCRYPT_AES256;
c_hmac_sha256 CONSTANT PLS_INTEGER := DBMS_CRYPTO.HMAC_SHA256;
c_iv_len CONSTANT PLS_INTEGER := 16; -- AES block size in bytes
c_hmac_len CONSTANT PLS_INTEGER := 32; -- SHA-256 output in bytes

-- ------------------------------------------------------------------------
-- PRIVATE: Base64 Encode (pure PL/SQL, no UTL_ENCODE dependency)
--------------------------------------------------------------------------
FUNCTION base64_encode(p_raw IN RAW) RETURN VARCHAR2 IS
BEGIN
RETURN UTL_ENCODE.BASE64_ENCODE(UTL_RAW.CAST_TO_VARCHAR2(p_raw));
END;

--------------------------------------------------------------------------
-- PRIVATE: Base64 Decode (pure PL/SQL, no UTL_ENCODE dependency)
--------------------------------------------------------------------------
FUNCTION base64_decode(p_b64 IN VARCHAR2) RETURN RAW IS
BEGIN
RETURN UTL_RAW.CAST_TO_RAW(UTL_ENCODE.BASE64_DECODE(p_b64));
END;

--------------------------------------------------------------------------
-- PRIVATE: Derive a 32-byte key from an arbitrary passphrase.
--------------------------------------------------------------------------
FUNCTION derive_key(p_key IN VARCHAR2) RETURN RAW IS
BEGIN
-- Hash the key material to a fixed 32-byte (AES-256) key.
RETURN DBMS_CRYPTO.HASH(
UTL_I18N.STRINGTORAW(p_key, 'UTF8'),
c_hmac_sha256
);
END derive_key;

--------------------------------------------------------------------------
-- PUBLIC: ENCRYPT
--------------------------------------------------------------------------
FUNCTION encrypt(
p_plain_text IN VARCHAR2,
p_key IN VARCHAR2,
p_iv IN VARCHAR2 DEFAULT NULL
) RETURN VARCHAR2 IS
l_key RAW(32);
l_iv RAW(16);
l_plaintext RAW;
l_ciphertext RAW;
l_hmac RAW(32);
l_combined RAW(32767);
l_b64 VARCHAR2(32767);
BEGIN
IF p_plain_text IS NULL OR p_key IS NULL THEN
RETURN NULL;
END IF;

-- Derive fixed-length key
l_key := derive_key(p_key);

-- Use provided IV or generate a random one
IF p_iv IS NOT NULL THEN
l_iv := UTL_I18N.STRINGTORAW(p_iv, 'UTF8');
ELSE
l_iv := DBMS_CRYPTO.RANDOMBYTES(c_iv_len);
END IF;

-- Convert plain text to RAW
l_plaintext := UTL_I18N.STRINGTORAW(p_plain_text, 'UTF8');

-- Encrypt with AES-256
l_ciphertext := DBMS_CRYPTO.ENCRYPT(
src => l_plaintext,
typ => c_aes256,
key => l_key,
iv => l_iv
);

-- Compute HMAC over (IV || ciphertext) for integrity
l_combined := l_iv || l_ciphertext;
l_hmac := DBMS_CRYPTO MAC(
src => l_combined,
typ => c_hmac_sha256,
key => l_key
);

-- Final payload: IV (16) || ciphertext (variable) || HMAC (32)
l_combined := l_iv || l_ciphertext || l_hmac;

-- Base64 encode the binary payload
l_b64 := base64_encode(l_combined);

RETURN l_b64;

END encrypt;

--------------------------------------------------------------------------
-- PUBLIC: DECRYPT
--------------------------------------------------------------------------
FUNCTION decrypt(
p_cipher_text IN VARCHAR2,
p_key IN VARCHAR2
) RETURN VARCHAR2 IS
l_key RAW(32);
l_combined RAW;
l_iv RAW(16);
l_ciphertext RAW;
l_hmac_stored RAW(32);
l_hmac_calc RAW(32);
l_plaintext RAW;
BEGIN
IF p_cipher_text IS NULL OR p_key IS NULL THEN
RETURN NULL;
END IF;

l_key := derive_key(p_key);

-- Base64 decode the input
l_combined := base64_decode(p_cipher_text);

IF UTL_RAW.LENGTH(l_combined) < c_iv_len + c_hmac_len THEN
RAISE_APPLICATION_ERROR(-20001, 'Invalid or truncated ciphertext.');
END IF;

-- Parse: IV (16) || ciphertext (rest - 32) || HMAC (32)
l_iv := UTL_RAW.SUBSTR(l_combined, 1, c_iv_len);
l_hmac_stored := UTL_RAW.SUBSTR(l_combined, UTL_RAW.LENGTH(l_combined) - c_hmac_len + 1, c_hmac_len);
l_ciphertext := UTL_RAW.SUBSTR(l_combined, c_iv_len + 1,
UTL_RAW.LENGTH(l_combined) - c_iv_len - c_hmac_len);

-- Verify HMAC
l_hmac_calc := DBMS_CRYPTO.MAC(
src => l_iv || l_ciphertext,
typ => c_hmac_sha256,
key => l_key
);

IF l_hmac_calc <> l_hmac_stored THEN
RAISE_APPLICATION_ERROR(-20002, 'HMAC verification failed " data may be tampered.');
END IF;

-- Decrypt
l_plaintext := DBMS_CRYPTO.DECRYPT(
src => l_ciphertext,
typ => c_aes256,
key => l_key,
iv => l_iv
);

-- Convert RAW back to UTF-8 VARCHAR2
RETURN UTL_I18N.RAWTOHEX(l_plaintext); -- placeholder; see note below

END decrypt;

END sec_crypto_pkg;
/

Now, we can try and compile this in my 26ai Free database:

C:\AI\AnythingLLM\pl_sql>sqlplus sys/oracle@//192.168.56.34:1521/freepdb1 as sysdba

SQL*Plus: Release 19.0.0.0.0 - Production on Wed Aug 19 12:49:35 2026
Version 19.28.0.0.0

Copyright (c) 1982, 2025, Oracle. All rights reserved.


Connected to:
Oracle AI Database 26ai Free Release 23.26.0.0.0 - Develop, Learn, and Run for Free
Version 23.26.0.0.0

SQL> @enc

Package created.

No errors.

Warning: Package Body created with compilation errors.

SQL> sho err
Errors for PACKAGE BODY SEC_CRYPTO_PKG:

LINE/COL ERROR
-------- -----------------------------------------------------------------
82/35 PLS-00103: Encountered the symbol "MAC" when expecting one of the
following:
. ( * @ % & = - + ; < / > at in is mod remainder not rem
<> or != or ~= >= <= <> and or like like2
like4 likec between || multiset member submultiset <=> <->
<#>
The symbol "." was substituted for "MAC" to continue.

SQL> 82
82* l_hmac := DBMS_CRYPTO MAC(
SQL>
So, unlike qwen2.5-coder and qwen3-coder there is no major hallucinated code; yet!! Let us look at the issue and see if it can be fixed:

...
l_hmac := DBMS_CRYPTO MAC(
src => l_combined,
typ => c_hmac_sha256,
key => l_key
);
...

The DOT is missing between the DBMS_CRYPTO package and the function MAC, in the decrypt function the DOT is not missing. So fixing that we can try again:

SQL> @enc

Package created.

No errors.

Warning: Package Body created with compilation errors.

SQL> sho err
Errors for PACKAGE BODY SEC_CRYPTO_PKG:

LINE/COL ERROR
-------- -----------------------------------------------------------------
0/0 PL/SQL: Compilation unit analysis terminated
7/57 PLS-00302: component 'HMAC_SHA256' must be declared
SQL>

So we can check the Oracle documents or look at the source code of DBMS_CRYPTO header for the constants and it should be HMAC_SH256 not HMAC_SHA256 - one letter error / hallucinated?

Fixed we can try again:

SQL> @enc

Package created.

No errors.

Warning: Package Body created with compilation errors.

SQL> sho err
Errors for PACKAGE BODY SEC_CRYPTO_PKG:

LINE/COL ERROR
-------- -----------------------------------------------------------------
33/9 PL/SQL: Statement ignored
34/29 PLS-00302: component 'STRINGTORAW' must be declared
49/21 PLS-00215: String length constraints must be in range (1 ..
32767)

50/22 PLS-00215: String length constraints must be in range (1 ..
32767)

64/13 PL/SQL: Statement ignored
64/30 PLS-00302: component 'STRINGTORAW' must be declared
70/9 PL/SQL: Statement ignored

LINE/COL ERROR
-------- -----------------------------------------------------------------
70/33 PLS-00302: component 'STRINGTORAW' must be declared
106/21 PLS-00215: String length constraints must be in range (1 ..
32767)

108/22 PLS-00215: String length constraints must be in range (1 ..
32767)

111/22 PLS-00215: String length constraints must be in range (1 ..
32767)

152/9 PL/SQL: Statement ignored

LINE/COL ERROR
-------- -----------------------------------------------------------------
152/25 PLS-00302: component 'RAWTOHEX' must be declared
SQL>

OK, it is time to stop the experiment and take stock. The STRINGTORAW functions should be STRING_TO_RAW but the RAWTOHEX does not exist at all in the UTL_I18N package. The five places where there is an index constraint issue is because RAW is used with no size.

At least qwen3.8:27b did not major hallucinate Oracle package names like its predecessor but it did get function names wrong but they were close or PL/SQL syntax wrong for RAW size constraints and more.

To be fair I did not change the system prompt to tell the model that it is a PL/SQL coder and to stick to syntax and so on. I think if I added a much better system prompt it could work.

Is it better than the 2.5 and 3 models; Yes, I think so and seemed faster. I could also change the temperature and the reasoning to get better code results BUT I think a much better system prompt would be better.

I have been also using gtp-oss:20b with a comprehensive set of changes, reasoning, temperature, system context and more and got very good results with coding in Lua where code compiled and ran every time so I think qwen3.8:27b with better tweaking looks promising for PL/SQL. More soon!!

#oracleace #oracleacepro #sym_42 #ai #llm #qwen38 #macbook #m5 #max

Find rules for a Command Rule in Database Vault

There are many components that are part of Database Vault. At the lowest level are factors that encapsulate the individual pieces of data that you may use in the rest of the set up. Then there are DV rules that return whether the rule passed or not and these can use factors. We also have rule sets that combine multiple rules together and can be all rules must pass or some or any rules can pass. We can then assign a rule set to a command rule, a realm or a secure application role.

So, we get a tree of sorts or three different trees, one for command rules, one for realms and one for secure application roles. Under these are rules in a rule set and under these are factors or code.

If we have a command rule such as ALTER USER then we might want to know what controls this at a Database Vault level. Of course to issue ALTER USER commands a user must also have the ALTER USER system privilege but how can we see the DV structure for the command rule ALTER USER?

First step is to look at the command rule view DBA_DV_COMMAND_RULE in my 21c DV enabled database:

SQL> @sc_print 'select * from dba_dv_command_rule where command =''''ALTER USER'''''
old 32: lv_str:=translate('&&1','''','''''');
new 32: lv_str:=translate('select * from dba_dv_command_rule where command =''ALTER USER''','''','''''');
Executing Query [select * from dba_dv_command_rule where command ='ALTER USER']
COMMAND : ALTER USER
CLAUSE_NAME : %
PARAMETER_NAME : %
EVENT_NAME : %
COMPONENT_NAME : %
ACTION_NAME : %
RULE_SET_NAME : Can Maintain Own Account
OBJECT_OWNER : %
OBJECT_NAME : %
ENABLED : Y
PRIVILEGE_SCOPE :
COMMON : NO
INHERITED : NO
ID# : 2
ORACLE_SUPPLIED : YES
PL_SQL_STACK : NO
-------------------------------------------

PL/SQL procedure successfully completed.

SQL>

There is only one ALTER USER command rule by default but there could be many command rules for the same command; for instance ALTER SYSTEM has 15 command rules:

SQL> col id# for 999
SQL> col clause_name for a20
SQL> col rule_set_name for a100
SQL> set lines 220
SQL> select id#,clause_name,rule_set_name from dba_dv_command_rule where command='ALTER SYSTEM';

ID# CLAUSE_NAME RULE_SET_NAME
---- -------------------- ----------------------------------------------------------------------------------------------------
24 SET Allow Fine Grained Control for Alter System
15 SET Disabled
19 SET Not allow to set AUDIT_SYS_OPERATIONS to False
20 SET Not allow to turn off AUDIT_TRAIL
13 SET Disabled
29 SET Disabled
28 SET Disabled
17 SET Not allow to set OPTIMIZER_SECURE_VIEW_MERGING to True
22 SET Not allow to set OS_ROLES to True
18 SET Not allow to set PLSQL_DEBUG to True
21 SET Not allow to set REMOTE_OS_ROLES to True

ID# CLAUSE_NAME RULE_SET_NAME
---- -------------------- ----------------------------------------------------------------------------------------------------
23 SET Not allow to set SQL92_SECURITY to False
26 SET Disabled
12 SET Disabled
25 DUMP Allow Dumping Datafile Header

15 rows selected.

SQL>

Notice that some rule sets are "disabled" so that the command rule does not fire. Some also have specific rule sets to control the use of the command rule.

The next step is to see the details of the rule set for the first example for ALTER USER. We can do this as follows:

SQL> @sc_print 'select * from dba_dv_rule_set where rule_set_name=''''Can Maintain Own Account'''''
old 32: lv_str:=translate('&&1','''','''''');
new 32: lv_str:=translate('select * from dba_dv_rule_set where rule_set_name=''Can Maintain Own Account''','''','''''');
Executing Query [select * from dba_dv_rule_set where rule_set_name='Can Maintain Own Account']
RULE_SET_NAME : Can Maintain Own Account
DESCRIPTION : Rule set that controls the roles that can manage user accounts and profiles or your own account.
ENABLED : Y
EVAL_OPTIONS_MEANING : Any True
AUDIT_OPTIONS : 1
FAIL_OPTIONS_MEANING : Show Error Message
FAIL_MESSAGE :
FAIL_CODE :
HANDLER_OPTIONS : 0
HANDLER :
IS_STATIC : FALSE
COMMON : NO
INHERITED : NO
ID# : 4
ORACLE_SUPPLIED : YES
-------------------------------------------

PL/SQL procedure successfully completed.

SQL>

Next we we need to use the link table to see the rule details for the rule set:

SQL> @sc_print 'select * from dba_dv_rule_set_rule where rule_set_name=''''Can Maintain Own Account'''''
old 32: lv_str:=translate('&&1','''','''''');
new 32: lv_str:=translate('select * from dba_dv_rule_set_rule where rule_set_name=''Can Maintain Own Account''','''','''''');
Executing Query [select * from dba_dv_rule_set_rule where rule_set_name='Can Maintain Own Account']
RULE_SET_NAME : Can Maintain Own Account
RULE_NAME : Is Alter DVSYS Allowed
RULE_EXPR : DVSYS.DBMS_MACADM.IS_ALTER_USER_ALLOW_VARCHAR('"'||dvsys.dv_login_user||'"') = 'Y'
ENABLED : Y
RULE_ORDER : 1
COMMON : NO
INHERITED : NO
-------------------------------------------
RULE_SET_NAME : Can Maintain Own Account
RULE_NAME : Login User Is Object User
RULE_EXPR : dvsys.dv_login_user = dvsys.dv_dict_obj_name
ENABLED : Y
RULE_ORDER : 1
COMMON : NO
INHERITED : NO
-------------------------------------------

PL/SQL procedure successfully completed.

SQL>

This gives us the expressions for the two rules used in the rule set that is attached to the command rule for ALTER USER. We can see that one rule checks that the user logged in is the object so not a pseudo user such as proxy or RAS or... The other rule calls a DBMS_MACADM function to check if the user is allowed to change their own account including changing their own password.

We can combine these simple checks into one SQL:

-- dv_cmd.sql
-- get command rule details

set lines 225
col command for a15
col clause_name for a8
col rule_set_name for a50
col rule_name for a30
col enabled for a1
col rule_expr_mod for a100 wrap

spool dv_cmd.lis
select c.command,
c.clause_name,
c.rule_set_name,
r.rule_name,
r.enabled,
replace(replace(r.rule_expr, chr(13)||chr(10), ':n'),
chr(13), ':n') as rule_expr_mod
from dba_dv_command_rule c,
dba_dv_rule_set_rule r
where r.rule_set_name=c.rule_set_name
order by c.id#
/
spool off

And sample output is:

SQL> @dv_cmd

COMMAND CLAUSE_N RULE_SET_NAME RULE_NAME E RULE_EXPR_MOD
--------------- -------- -------------------------------------------------- ------------------------------ - ----------------------------------------------------------------------------------------------------
CREATE USER % Can Maintain Accounts/Profiles Is User Manager Y DVSYS.DBMS_MACUTL.ROLE_GRANTED_ENABLED_VARCHAR('DV_ACCTMGR','"'||dvsys.dv_login_user||'"', 1, dvsys.
get_required_scope) = 'Y'

CREATE USER % Can Maintain Accounts/Profiles Is Drop User Allowed Y DVSYS.DBMS_MACADM.IS_DROP_USER_ALLOW_VARCHAR('"'||dvsys.dv_login_user||'"') = 'Y'
ALTER USER % Can Maintain Own Account Login User Is Object User Y dvsys.dv_login_user = dvsys.dv_dict_obj_name
ALTER USER % Can Maintain Own Account Is Alter DVSYS Allowed Y DVSYS.DBMS_MACADM.IS_ALTER_USER_ALLOW_VARCHAR('"'||dvsys.dv_login_user||'"') = 'Y'
DROP USER % Can Maintain Accounts/Profiles Is User Manager Y DVSYS.DBMS_MACUTL.ROLE_GRANTED_ENABLED_VARCHAR('DV_ACCTMGR','"'||dvsys.dv_login_user||'"', 1, dvsys.
get_required_scope) = 'Y'

DROP USER % Can Maintain Accounts/Profiles Is Drop User Allowed Y DVSYS.DBMS_MACADM.IS_DROP_USER_ALLOW_VARCHAR('"'||dvsys.dv_login_user||'"') = 'Y'
CREATE PROFILE % Can Maintain Accounts/Profiles Y DVSYS.DBMS_MACADM.IS_DROP_USER_ALLOW_VARCHAR('"'||dvsys.dv_login_user||'"') = 'Y'

COMMAND CLAUSE_N RULE_SET_NAME RULE_NAME E RULE_EXPR_MOD
--------------- -------- -------------------------------------------------- ------------------------------ - ----------------------------------------------------------------------------------------------------
CREATE PROFILE % Can Maintain Accounts/Profiles Is User Manager Y DVSYS.DBMS_MACUTL.ROLE_GRANTED_ENABLED_VARCHAR('DV_ACCTMGR','"'||dvsys.dv_login_user||'"', 1, dvsys.
get_required_scope) = 'Y'

ALTER PROFILE % Can Maintain Accounts/Profiles Is Drop User Allowed Y DVSYS.DBMS_MACADM.IS_DROP_USER_ALLOW_VARCHAR('"'||dvsys.dv_login_user||'"') = 'Y'
ALTER PROFILE % Can Maintain Accounts/Profiles Is User Manager Y DVSYS.DBMS_MACUTL.ROLE_GRANTED_ENABLED_VARCHAR('DV_ACCTMGR','"'||dvsys.dv_login_user||'"', 1, dvsys.
get_required_scope) = 'Y'

DROP PROFILE % Can Maintain Accounts/Profiles Is Drop User Allowed Y DVSYS.DBMS_MACADM.IS_DROP_USER_ALLOW_VARCHAR('"'||dvsys.dv_login_user||'"') = 'Y'
DROP PROFILE % Can Maintain Accounts/Profiles Is User Manager Y DVSYS.DBMS_MACUTL.ROLE_GRANTED_ENABLED_VARCHAR('DV_ACCTMGR','"'||dvsys.dv_login_user||'"', 1, dvsys.
get_required_scope) = 'Y'


COMMAND CLAUSE_N RULE_SET_NAME RULE_NAME E RULE_EXPR_MOD
--------------- -------- -------------------------------------------------- ------------------------------ - ----------------------------------------------------------------------------------------------------
CHANGE PASSWORD % Can Maintain Own Account Login User Is Object User Y dvsys.dv_login_user = dvsys.dv_dict_obj_name
CHANGE PASSWORD % Can Maintain Own Account Is Alter DVSYS Allowed Y DVSYS.DBMS_MACADM.IS_ALTER_USER_ALLOW_VARCHAR('"'||dvsys.dv_login_user||'"') = 'Y'
ALTER SYSTEM SET Disabled False Y 1=0
ALTER SYSTEM SET Disabled Y 1=0
ALTER SYSTEM SET Disabled Y 1=0
ALTER SYSTEM SET Not allow to set OPTIMIZER_SECURE_VIEW_MERGING to Is Parameter Value Not True Y UPPER(DVSYS.parameter_value) <> 'TRUE'
True

ALTER SYSTEM SET Not allow to set PLSQL_DEBUG to True Y UPPER(DVSYS.parameter_value) <> 'TRUE'
ALTER SYSTEM SET Not allow to set AUDIT_SYS_OPERATIONS to False Is Parameter Value Not False Y UPPER(DVSYS.parameter_value) <> 'FALSE'
ALTER SYSTEM SET Not allow to turn off AUDIT_TRAIL Is Parameter Value Not Off Y UPPER(DVSYS.parameter_value) <> 'OFF'

COMMAND CLAUSE_N RULE_SET_NAME RULE_NAME E RULE_EXPR_MOD
--------------- -------- -------------------------------------------------- ------------------------------ - ----------------------------------------------------------------------------------------------------
ALTER SYSTEM SET Not allow to turn off AUDIT_TRAIL Is Parameter Value Not None Y UPPER(DVSYS.parameter_value) <> 'NONE'
ALTER SYSTEM SET Not allow to set REMOTE_OS_ROLES to True Is Parameter Value Not True Y UPPER(DVSYS.parameter_value) <> 'TRUE'
ALTER SYSTEM SET Not allow to set OS_ROLES to True Y UPPER(DVSYS.parameter_value) <> 'TRUE'
ALTER SYSTEM SET Not allow to set SQL92_SECURITY to False Is Parameter Value Not False Y UPPER(DVSYS.parameter_value) <> 'FALSE'
ALTER SYSTEM SET Allow Fine Grained Control for Alter System Are Dump Parameters Allowed Y DVSYS.parameter_name = 'MAX_DUMP_FILE_SIZE' OR DVSYS.parameter_name = '_LOG_SEGMENT_DUMP_PATCH' OR D
VSYS.parameter_name = '_LOG_SEGMENT_DUMP_PARAMETER' OR DVSYS.parameter_name NOT LIKE '%DUMP%'

ALTER SYSTEM SET Allow Fine Grained Control for Alter System Are Dest Parameters Allowed Y DVSYS.parameter_name = 'STANDBY_ARCHIVE_DEST' OR DVSYS.parameter_name = 'DB_RECOVERY_FILE_DEST_SIZE'
OR DVSYS.parameter_name LIKE '%LOG_ARCHIVE_DEST%' OR DVSYS.parameter_name LIKE '%CURSOR_BIND_CAPTUR
E_DESTINATION%' OR DVSYS.parameter_name NOT LIKE '%_DEST%'


COMMAND CLAUSE_N RULE_SET_NAME RULE_NAME E RULE_EXPR_MOD
--------------- -------- -------------------------------------------------- ------------------------------ - ----------------------------------------------------------------------------------------------------
ALTER SYSTEM DUMP Allow Dumping Datafile Header Is Dump of Block Allowed Y DVSYS.dbms_macutl.alter_system_dump_varchar = 'Y'
ALTER SYSTEM SET Disabled False Y 1=0
ALTER SYSTEM SET Disabled Y 1=0
ALTER SYSTEM SET Disabled Y 1=0

31 rows selected.

SQL>

Database Vault can be sometimes be complex to set up and use and to understand what is controlling what.


#oracleace #oracleacepro #sym_42 #oracle #database #vault #dv #command #rule