Skip to content

perf: avoid unconditional chmod -R in enforce_misp_data_permissions#378

Merged
ostefano merged 4 commits intoMISP:masterfrom
LSI-Bayern:master
Mar 13, 2026
Merged

perf: avoid unconditional chmod -R in enforce_misp_data_permissions#378
ostefano merged 4 commits intoMISP:masterfrom
LSI-Bayern:master

Conversation

@LSI-ZuagrastaWastl
Copy link
Contributor

@LSI-ZuagrastaWastl LSI-ZuagrastaWastl commented Feb 26, 2026

Problem

On deployments with large app/files volumes (tested with ~90GB),
enforce_misp_data_permissions() in entrypoint_nginx.sh caused significant
startup delays on every docker compose up – even when permissions were already correct.

Root causes

  1. chmod -R u+w,g+w <dir> ran unconditionally over every file on every start,
    regardless of whether permissions were already correct.

  2. -not -perm 770 without a leading / or - is an exact octal match in find,
    not a bitmask check. Files with broader permissions like 0777 or 0755 were
    unnecessarily touched every run.

Fix

Replace all unconditional chmod -R calls with targeted find expressions using
exact ! -perm XXXX matching:

  • find -type f ! -perm 0550 -exec chmod 0550 {} +
  • find -type d ! -perm 0770 -exec chmod 0770 {} +

! -perm 0550 (without / or -) is an exact match in find – it catches both directions:

  • Files with too few bits (e.g. 0444) → corrected ✅
  • Files with too many bits (e.g. 0777) → corrected ✅
  • Files already at exactly 0550 → skipped entirely ✅

The chmod -R u+w,g+w lines are removed entirely as they are now fully covered
by the exact permission matching above. Notably the original chmod -R u+w,g+w
would never have corrected 0777 back to 0550 either – so the new fix is
strictly more correct in that regard as well.

Result

  • Steady state (permissions already correct): find traverses files but makes
    ~0 chmod syscalls → startup reduced from several minutes to seconds
  • First start / after version update: identical behaviour, all files corrected
  • Excess permissions (e.g. 0777): now explicitly corrected, which the original did not do

Open discussion

@ostefano raised a valid objection:
What if a dir is set to be 755, but then, something or someone chmod it to 777?
I believe the original spirit of this "enforcing phase" was exactly to fix these issues because MISP was not to be trusted 100% on this when operating on shared volumes (docker limitations).

But this was not taken with the original solution chmod -R u+w,g+w .

But the Fix

  • find -type f ! -perm 0550 -exec chmod 0550 {} +
  • find -type d ! -perm 0770 -exec chmod 0770 {} +
    should also fixes this case.

…are actually incorrect

Replace the three unconditional `chmod -R` calls with targeted `find` expressions that only touch files whose permissions are actually incorrect.
@ostefano
Copy link
Collaborator

Wow TIL.

Will test and merge it at the next opportunity.

LGTM, thanks!

@ostefano
Copy link
Collaborator

ostefano commented Mar 7, 2026

I have a concern now that I think of it.

What if a dir is set to be 755, but then, something or someone chmod it to 777?
I believe the original spirit of this "enforcing phase" was exactly to fix these issues because MISP was not to be trusted 100% on this when operating on shared volumes (docker limitations).

`-not -perm /0550` to ` ! -perm 0550` (exactly Match)
`-not -perm /0770`  to `! -perm 0770` (exactly Match)
`-type f / -type d`  ( find filter for file type)
- delete `chmod -R u+w,g+w` lines – because its redundant to ` ! -perm 0550/0770`
@LSI-ZuagrastaWastl
Copy link
Contributor Author

You're right that find \( ! -perm -u+w -o ! -perm -g+w \) only catches missing bits, not excess bits like 0777. The fix is to use exact matching with ! -perm 0550 for files and ! -perm 0770 for directories – this catches both directions (too few AND too many permission bits) while still skipping files that are already correct. Only the files that already have the exact target permissions are skipped, which is the intended steady-state behaviour.

That would be even stricter/better than in the original. :) chmod -R u+w,g+w would never have corrected 0777, but this did it.

@ostefano
Copy link
Collaborator

This is becoming difficult to review.

Can you retrofit our discussion in the main description of the issue so we can ask other folks to review?

Thanks!

Copy link
Collaborator

@ostefano ostefano left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my other comment

@LSI-ZuagrastaWastl
Copy link
Contributor Author

Okay, I'll do it, or rather, the AI will ;) I hope the new description is what you had in mind?

@ostefano
Copy link
Collaborator

Thanks, I will give it a proper look later today 👍

@UFOSmuggler could you do a pass here as well (rather critical area)

@UFOSmuggler
Copy link
Contributor

Ok so to me it looks like the real issue is that after some find -type f -exec chmod 550 changes, we ALSO do a recursive chmod that OVERWRITES the 550 and sets 770, example:

find /var/www/MISP/app/tmp \( ! -user www-data -or ! -group www-data \) -exec chown www-data:www-data {} +
find /var/www/MISP/app/tmp -not -perm 550 -type f -exec chmod 0550 {} +
find /var/www/MISP/app/tmp -not -perm 770 -type d -exec chmod 0770 {} +
chmod -R u+w,g+w /var/www/MISP/app/tmp

This means every container start, we change the file perms in some paths twice. presumably this is an accident.

We make this possible mistake in the following locations:

  • /var/www/MISP/app/tmp
  • /var/www/MISP/app/files

Removing ONE of these recursive file mode changes is a good move.

However, which to remove? Do we want 0550 or 0770?

It looks like 0770 is needed at least in SOME locations. I don't yet know why, but if I spin up a fresh container using this PR, it starts healthily. I then start pulling the circl.lu feed and it works.

I then restart the container, and it spams a lot of shit into the logs:

misp-core-1  | Updating /var/www/MISP/app/Lib/cakephp/lib/Cake/Config/cacert.pem using curl data...
misp-core-1  | MISP | Apply minimum configuration directives ...
misp-core-1  | PHP Warning:  SplFileInfo::openFile(/var/www/MISP/app/tmp/cache/persistent/myapp_cake_core_file_map): Failed to open stream: Permission denied in /var/www/MISP/app/Lib/cakephp/lib/Cake/Cache/Engine/FileEngine.php on line 364
misp-core-1  | PHP Warning:  SplFileInfo::openFile(/var/www/MISP/app/tmp/cache/persistent/myapp_cake_core_cake_console_eng): Failed to open stream: Permission denied in /var/www/MISP/app/Lib/cakephp/lib/Cake/Cache/Engine/FileEngine.php on line 364
misp-core-1  | PHP Warning:  SplFileInfo::openFile(/var/www/MISP/app/tmp/cache/persistent/myapp_cake_core_cake_console_eng): Failed to open stream: Permission denied in /var/www/MISP/app/Lib/cakephp/lib/Cake/Cache/Engine/FileEngine.php on line 364
misp-core-1  | PHP Warning:  _cake_core_ cache was unable to write 'cake_console_eng' to File cache in /var/www/MISP/app/Lib/cakephp/lib/Cake/Cache/Cache.php on line 320
misp-core-1  | Warning Error: SplFileInfo::openFile(/var/www/MISP/app/tmp/cache/persistent/myapp_cake_core_default_eng): Failed to open stream: Permission denied in [/var/www/MISP/app/Lib/cakephp/lib/Cake/Cache/Engine/FileEngine.php, line 364]
misp-core-1  | 
misp-core-1  | 2026-03-12 04:57:10 Warning: SplFileInfo::openFile(/var/www/MISP/app/tmp/cache/persistent/myapp_cake_core_default_eng): Failed to open stream: Permission denied in [/var/www/MISP/app/Lib/cakephp/lib/Cake/Cache/Engine/FileEngine.php, line 364]
misp-core-1  | Warning Error: SplFileInfo::openFile(/var/www/MISP/app/tmp/cache/persistent/myapp_cake_core_cake_console_eng): Failed to open stream: Permission denied in [/var/www/MISP/app/Lib/cakephp/lib/Cake/Cache/Engine/FileEngine.php, line 364]

And so on... I don't yet know why this is... I assume maybe cake console wants to write shit as www-data in app/tmp or app/files, but can't open an existing file for write as it is 550? I will need to hunt this down.

Otherwise the diff seems to show stylistic differences in the syntax of the commands which are as far as i can tell functionally equivalent, with some possible portability differences.

ESOTERIC AND DOESN'T REALLY MATTER:

I wondered if this made a difference, which it doesn't really appear to, except perhaps for posix portability in at least one case.

Using the app/files one as an example:

Old: find /var/www/MISP/app/files -not -perm 550 -type f -exec chmod 0550 {} +
New: find /var/www/MISP/app/files -type f ! -perm 0550 -exec chmod 0550 {} +

! vs -not seems to also have portability improvements according to the manual, but otherwise be the same:

       ! expr True if expr is false.  This character will also usually need protection from interpretation by the shell.

       -not expr
              Same as ! expr, but not POSIX compliant.

0550 vs 550 are functionally the same, as 0550 is just 550 in octal, but chmod already considers 550 octal, and find handles these in exactly the same way.

I assume the thinking was "find files without the special bit set", but this is what already happens with 550:

davidz@ocp:/data/local/findtest/files/100$ chmod 1550 file_100
davidz@ocp:/data/local/findtest/files/100$ stat file_100
  File: file_100
  size: 0         	Blocks: 0          IO Block: 4096   regular empty file
Device: 10307h/66311d	Inode: 209322872   Links: 1
Access: (1550/-r-xr-x--T)  Uid: ( 1000/  davidz)   Gid: ( 1000/  davidz)
Access: 2026-03-12 14:36:56.615549892 +1100
Modify: 2026-03-12 14:36:56.615549892 +1100
Change: 2026-03-12 15:05:31.816642973 +1100
 Birth: 2026-03-12 14:36:56.615549892 +1100

davidz@ocp:/data/local/findtest/files/100$ cd ../..
davidz@ocp:/data/local/findtest$ time find files -type f ! -perm 0550
files/100/file_100

real	0m1.628s
user	0m0.314s
sys	0m1.311s
davidz@ocp:/data/local/findtest$ time find files -type f ! -perm 550
files/100/file_100

real	0m1.616s
user	0m0.388s
sys	0m1.226s

The only other real difference seems to be the order of the file type and permission tests, which don't seem to matter very much. Each version of the command with -O0 or -O3 seems to perform basically the same as well (query optimisation), so I guess neither test is particularly costly.

I created a million empty files over a thousand directories and tested the two (on a very fast pcie5 nvme disk):

davidz@ocp:/data/local/findtest$ find files -type d|wc -l
1001
davidz@ocp:/data/local/findtest$ find files -type f|wc -l
1000000
davidz@ocp:/data/local/findtest$ time find files -not -perm 550 -type f -exec chmod 0550 {} +

real	0m6.300s   <--- first run, actually had to set the permissions
user	0m0.961s
sys	0m5.331s
davidz@ocp:/data/local/findtest$ time find files -not -perm 550 -type f -exec chmod 0550 {} +

real	0m1.662s
user	0m0.398s
sys	0m1.262s
davidz@ocp:/data/local/findtest$ time find files -type f ! -perm 0550 -exec chmod 0550 {} +

real	0m1.637s
user	0m0.406s
sys	0m1.230s
davidz@ocp:/data/local/findtest$ time find files -type f ! -perm 0550 -exec chmod 0550 {} +

real	0m1.599s
user	0m0.350s
sys	0m1.248s
davidz@ocp:/data/local/findtest$ time find files -not -perm 550 -type f -exec chmod 0550 {} +

real	0m1.621s
user	0m0.383s
sys	0m1.236s

@UFOSmuggler
Copy link
Contributor

I don't think I can look further into this until tomorrow.

@iglocska
Copy link
Member

770 will indeed be needed for quite a few files in there. The cache files for one, are frequently updated by the framework. Those directories also include temporary files used for exports, sadly it's a pretty bad mish-mash of script files and temporary user data. One thing that could work is setting 770 on the entire directory and 550 on anything .py - though 770 is pretty low risk too.

@UFOSmuggler
Copy link
Contributor

getting rid of the non-find chmod is the right move, and maybe just do one find chmod to enforce 770 on both dirs and files, where appropriate. no -type parameter.

770 is what we already had, is not a step backwards.

we can do further tightening work later to address what has been illuminated by this PR.

@iglocska
Copy link
Member

*Also, add mental note to correctly split data from scripts in files for easier maintenance in 3.x

@UFOSmuggler
Copy link
Contributor

*Also, add mental note to correctly split data from scripts in files for easier maintenance in 3.x

great idea, make an issue or jira ticket or whatever you guys use.

harder to misplace than mental notes. at least in my considerable career of misplacing mental notes.

@LSI-ZuagrastaWastl
Copy link
Contributor Author

Thank you, @UFOSmuggler, for the excellent and interesting analysis, and thank you, @iglocska , for your additions.
I also find it interesting that the chmod -R u+w,g+w /var/www/MISP/app/file indirectly or possibly unknowingly solved a permission problem. Of course, I didn't verify that again (-.-), but focused primarily on performance.

The comparison between ! and -not is also interesting, but I would prefer ! for POSIX compliance.

I strongly support the suggestion regarding the separation of data and scripts in 3.x.

However, I will now take it upon myself to favor the following solution and adapt it accordingly.

  • app/tmp + app/files → everything 0770 without -type, a single find per directory – exactly what ufoSmuggler suggested
  • app/Config → remains differentiated: Files 0550, Dirs 0770 – Config files should not be writable
  • chmod -R over all files in the end, completely gone – that was the original perfomance problem

solution from discussion:

-  app/tmp + app/files → everything 0770 without -type, a single find per directory – exactly what ufoSmuggler suggested
-  app/Config → remains differentiated: Files 0550, Dirs 0770 – Config files should not be writable
- chmod -R over all files in the end, completely gone – that was the original perfomance problem
@UFOSmuggler
Copy link
Contributor

this looks good to me now.

thanks for your work @LSI-ZuagrastaWastl 🫡

Copy link
Contributor

@UFOSmuggler UFOSmuggler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@ostefano ostefano merged commit 47f5c47 into MISP:master Mar 13, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants