Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix msfdb init command failure in systems that use the 'pg_ctl.rb' msfdb_helper #16094

Merged
merged 9 commits into from
Jul 27, 2022

Conversation

3V3RYONE
Copy link
Contributor

This PR fixes #16086

Cause of bug

The cause of this issue is because of the default path of unix_socket_directories in the postgresql.conf file. The default path is set to /run/postgresql, and this is where the pg tries to create the socket file for starting the server. But the current user who runs ./msfdb init (and not sudo ./msfdb init) does not have the privileges to access the /run directory. Hence, the socket file is not created and the server start up fails.

Approach

There were two approaches to solve this issue. One was to run the commands as the super user, by changing the internal commands from pg_ctl -D -l start to sudo pg_ctl -D -l start. This way, the super user could access the /run/postgresql directory and the socket could be created.

However, the other and the better approach was to change the location of unix_socket_directories from /run/postgresql to /tmp. This way, the normal user can create the socket files and make the server started. So, I created an additional parameter @options[:unix_socket_directories] in the msfdb file which would contain the new socket path along with other db confs. and appended this updated directory path (along with the updated port 5433) in the postgresql.conf file. The db_interface.rb file was also updated, where I added the -h '/tmp' argument in the psql commands to specify it the updated socket directory.

This worked pretty well in linux but failed in windows. This was because there is no /tmp location in windows. So, to solve this, I first checked in the postgresql.conf file, that whether the default location is set to /run/postgresql or '' (for windows), using a regex. Only in the case of a regex match, the pg_ctl.rb file appends both updated path as well as updated port to the postgresql.conf file, else it only appends the updated port. This seems to work well in both systems now!

Before

./msfdb init
[?] Would you like to init the webservice? (Not Required) [no]: no
Clearing http web data service credentials in msfconsole
Running the 'init' command for the database:
Creating database at /home/beleswar/.msf4/db
Starting database at /home/beleswar/.msf4/db...failed
Creating database users
/home/beleswar/.local/share/gem/ruby/3.0.0/gems/pg-1.2.3/lib/pg.rb:58:in `initialize': could not connect to server: Connection refused (PG::ConnectionBad)
	Is the server running on host "127.0.0.1" and accepting
	TCP/IP connections on port 5433?  
./msfdb init
[?] Would you like to init the webservice? (Not Required) [no]: no
Clearing http web data service credentials in msfconsole
Running the 'init' command for the database:
Creating database at /home/beleswar/.msf4/db
Starting database at /home/beleswar/.msf4/db...success
Creating database users
/home/beleswar/.local/share/gem/ruby/3.0.0/gems/pg-1.2.3/lib/pg.rb:58:in `initialize': FATAL:  role "msf" does not exist (PG::ConnectionBad)  

After

[beleswar@Som metasploit-framework]$ ./msfdb init
[?] Would you like to init the webservice? (Not Required) [no]: no
Clearing http web data service credentials in msfconsole
Running the 'init' command for the database:
Creating database at /home/beleswar/.msf4/db
Starting database at /home/beleswar/.msf4/db...success
Creating database users
Writing client authentication configuration file /home/beleswar/.msf4/db/pg_hba.conf
Stopping database at /home/beleswar/.msf4/db
Starting database at /home/beleswar/.msf4/db...success
Creating initial database schema

Verification

  • git clone the msf repository in a linux system and setup the environment (bundle install)
  • cd metasploit-framework, change into installed directory
  • Replace the original msfdb, lib/msfdb_helpers/pg_ctl.rb, lib/msfdb_helpers/db_interface.rb files with the files in this PR.
  • run ./msfdb init
  • Type no for init webservices, as its not necessary
  • Verify that the script succeeds in initializing the database without any error.
  • Verify that the script does not fail with an error message.

Note

The above changes are also tested in windows environment. The server startup failure was already present in windows environment.
Log:

C:\Users\IEUser>msfdb init
C:/metasploit-framework/embedded/lib/ruby/gems/3.0.0/gems/zeitwerk-2.5.3/lib/zeitwerk/kernel.rb:35: warning: Win32API is deprecated after Ruby 1.9.1; use fiddle directly instead
[?] Would you like to init the webservice? (Not Required) [no]: no
Clearing http web data service credentials in msfconsole
Running the 'init' command for the database:
Creating database at C:/Users/IEUser/.msf4/db
Starting database at C:/Users/IEUser/.msf4/db...failed
Creating database users
Writing client authentication configuration file C:/Users/IEUser/.msf4/db/pg_hba.conf
Database is no longer running at C:/Users/IEUser/.msf4/db
Starting database at C:/Users/IEUser/.msf4/db...failed
Creating initial database schema  

@smcintyre-r7
Copy link
Contributor

Instead of analyzing the configuration file to determine if the platform is Windows or Linux, can you just use Dir.tmpdir and always pass it as the -h option? That would dramatically simplify this code because you would just always use the temporary directory that Ruby provides you. You wouldn't need to check the existing config file contents, you wouldn't need any kind of conditional statement and you wouldn't need a new config option that may or may not be used.

I'm looking at PostgreSQL on Windows right now and it looks like psql.exe takes the -h argument like its Linux counter part does.

@smcintyre-r7 smcintyre-r7 self-assigned this Jan 24, 2022
@3V3RYONE
Copy link
Contributor Author

Okay! Thats a really good idea. I'll work on it and make the commit!
Thanks :D

@3V3RYONE
Copy link
Contributor Author

This idea really simplified the code, and works well in both the systems! Thanks! :D

@3V3RYONE
Copy link
Contributor Author

Any updates on this @smcintyre-r7 ?

@jmartin-tech
Copy link
Contributor

A note for anyone testing this, change need to be tested on macOS for x64 as pkg builds are part of the list of nightly builds.

@smcintyre-r7
Copy link
Contributor

I wasn't able to reproduce the original issue. From what you reported it sounds like it should be as simple as using a fresh install on Linux and running msfdb init as a non-root user. When I tried that from within an Ubuntu docker container, everything worked as intended.

Can you provide some additional steps to reproduce the issue that this is intended to fix to demonstrate that the changes do so?

@3V3RYONE
Copy link
Contributor Author

3V3RYONE commented Feb 1, 2022

Did you use the nightly-installers to install MSF or did you git clone the repo?
May you once try to git clone the repo and then run ./msfdb init if that works to reproduce the issue.

@smcintyre-r7
Copy link
Contributor

It was from a git clone. I attached the docker file I used and the output.

Dockerfile
# Originally used while testing https://github.com/rapid7/metasploit-framework/pull/16094

FROM ubuntu:latest
MAINTAINER Spencer McIntyre <zeroSteiner@gmail.com> (@zeroSteiner)
ARG DEBIAN_FRONTEND=noninteractive

RUN useradd -m -s /bin/bash metasploit
RUN apt-get update && \
        apt-get -y install build-essential bundler git ruby vim && \
        apt-get clean
RUN git clone https://github.com/rapid7/metasploit-framework.git
RUN apt-get -y install libpq-dev libsqlite3-dev libpcap-dev postgresql-12
WORKDIR /metasploit-framework
RUN bundle install
docker run --rm -it metasploit:latest /bin/bash
root@39676b04015b:/metasploit-framework# su metasploit
metasploit@39676b04015b:/metasploit-framework$ ./msfdb init
/metasploit-framework/lib/msf/core/exploit.rb:65: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/metasploit-framework/lib/msf/core/exploit.rb:103: warning: The called method `initialize' is defined here
/metasploit-framework/lib/msf/core/exploit.rb:69: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/metasploit-framework/lib/msf/core/exploit.rb:103: warning: The called method `initialize' is defined here
/metasploit-framework/lib/msf/core/exploit.rb:73: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/metasploit-framework/lib/msf/core/exploit.rb:103: warning: The called method `initialize' is defined here
/metasploit-framework/lib/msf/core/exploit.rb:77: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/metasploit-framework/lib/msf/core/exploit.rb:103: warning: The called method `initialize' is defined here
/metasploit-framework/lib/msf/core/exploit.rb:81: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/metasploit-framework/lib/msf/core/exploit.rb:103: warning: The called method `initialize' is defined here
/metasploit-framework/lib/msf/core/exploit.rb:85: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/metasploit-framework/lib/msf/core/exploit.rb:103: warning: The called method `initialize' is defined here
[?] Would you like to init the webservice? (Not Required) [no]: no
Clearing http web data service credentials in msfconsole
Running the 'init' command for the database:
Creating database at /home/metasploit/.msf4/db
Starting database at /home/metasploit/.msf4/db...success
Creating database users
Writing client authentication configuration file /home/metasploit/.msf4/.local/etc/postgresql/12/msf/pg_hba.conf
Creating initial database schema
metasploit@39676b04015b:/metasploit-framework$ 

What version of Linux were you using when you ran into the issue originally? Is it possible it was broken because the file system permissions on the /run directory are incorrect?

@3V3RYONE
Copy link
Contributor Author

3V3RYONE commented Feb 1, 2022

Hello @smcintyre-r7 , I'm running Arch linux as my main machine and I am still able to reproduce this issue. For re-checking, I opened up my old laptop running Arch Linux as well, and this issue still persists there. (I'm using the latest version of MSF & linux both)

Console output in old computer:

[beleswar@Som metasploit-framework]$ ./msfdb init
[?] Would you like to init the webservice? (Not Required) [no]: no
Clearing http web data service credentials in msfconsole
Running the 'init' command for the database:
Creating database at /home/beleswar/.msf4/db
Starting database at /home/beleswar/.msf4/db...failed
Creating database users
/usr/lib/ruby/gems/3.0.0/gems/pg-1.3.0/lib/pg/connection.rb:636:in `async_connect_or_reset': could not connect to server: Connection refused (PG::ConnectionBad)
	Is the server running on host "127.0.0.1" and accepting
	TCP/IP connections on port 5433?
	from /usr/lib/ruby/gems/3.0.0/gems/pg-1.3.0/lib/pg/connection.rb:705:in `new'
	from /usr/lib/ruby/gems/3.0.0/gems/pg-1.3.0/lib/pg.rb:67:in `connect'
	from /home/beleswar/check/metasploit-framework/lib/msfdb_helpers/pg_ctl.rb:105:in `create_db_users'
	from /home/beleswar/check/metasploit-framework/lib/msfdb_helpers/pg_ctl.rb:25:in `init'
	from ./msfdb:201:in `init_db'
	from ./msfdb:938:in `invoke_

Do you think this is something local to my computer? I guess gwillcox-r7 was also able to produce this in ubuntu.. (https://metasploit.slack.com/archives/CCN8L75UN/p1642017073011000?thread_ts=1642010086.009600&cid=CCN8L75UN)

@lilithium-hydride
Copy link

For what it's worth, I'm on Arch as well (well, Artix, but close enough). I installed MSF for the first time on it and was able to reproduce the issue. Modifying the files in /opt in accordance with this PR fixed it. Could it be a version of some tangentially related package that's more recent on Arch?

@3V3RYONE
Copy link
Contributor Author

3V3RYONE commented Feb 2, 2022

Could it be a version of some tangentially related package that's more recent on Arch?

Well possible, I'll try to reproduce it on ubuntu today then..

@jmartin-tech
Copy link
Contributor

Some added context Arch linux maintains its own packaging process. While Metasploit strives to maintain compatibility with a wide range to linux distos packaging can introduce variation in configuration requirements.

@smcintyre-r7
Copy link
Contributor

@lilithium-hydride thanks alot for chiming in. It's always super helpful to confirm an issue can reproduced by multiple people and I'll definitely take a much closer look at Arch now.

@3V3RYONE
Copy link
Contributor Author

3V3RYONE commented Feb 2, 2022

I am also checking to reproduce this issue in an ubuntu VM.. If it happens to be an issue just local to Arch I guess we wouldn't need to make specific adjustments 😁

@3V3RYONE
Copy link
Contributor Author

3V3RYONE commented Feb 3, 2022

Hello, so I tried to replicate this issue in ubuntu, but failed to do so. I also just found why this issue isn't showing up in ubuntu.

  1. So in the metasploit-framework/lib/msfdb_helpers directory, we have two helpers, pg_ctl.rb and pg_ctlcluster.rb. As discussed above, the cause of this issue is the location of unix_socket_directories to be inaccessible by the non-root user. In pg_ctlcluster.rb file, we already have this change implemented. (pg_ctlcluster.rb). That is, the cluster is created with the socket_directory specified as /tmp directory (achieved by the -s flag, bolded below).

run_cmd("pg_createcluster --user=$(whoami) -l #{@db.shellescape}/log -d #{@db.shellescape} -s /tmp --encoding=UTF8 #{@pg_version} #{options[:msf_db_name].shellescape} -- --username=$(whoami) --auth-host=trust --auth-local=trust")

  1. Whereas, in the pg_ctl.rb file, this change is not already implemented. (pg_ctl.rb). That is, the default socket_directory is not changed here while initializing the db.

run_cmd("initdb --auth-host=trust --auth-local=trust -E UTF8 #{@db.shellescape}")

  1. So, Ubuntu is using this pg_ctlcluster.rb helper while running ./msfdb init wherease Arch is using the pg_ctl.rb helper while running ./msfdb init. This is why we don't get the issue in Ubuntu but we get it in Arch.

The above point can be verified by running ./msfdb init in the debug mode. For that, set the debug parameter to true in your metasploit-framework/msfdb file (msfdb) and then run ./msfdb init.

Ubuntu Output in debug mode:

./msfdb init
[?] Would you like to init the webservice? (Not Required) [no]:
Clearing http web data service credentials in msfconsole
run_cmd: cmd=msfconsole -qx 'db_disconnect --clear; exit', input=, env={}
'msfconsole -qx 'db_disconnect --clear; exit'' returned 127
sh: 1: msfconsole: not found
run_cmd: cmd=msfconsole -qx 'db_disconnect --clear; exit', input=, env={"PATH"=>".:/var/lib/gems/2.7.0/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin"}
'msfconsole -qx 'db_disconnect --clear; exit'' returned 0
No default data service was configured.

Running the 'init' command for the database:
Creating database at /home/beleswar/.msf4/db
run_cmd: cmd=pg_createcluster --user=$(whoami) -l /home/beleswar/.msf4/db/log -d /home/beleswar/.msf4/db -s /tmp --encoding=UTF8 12 msf -- --username=$(whoami) --auth-host=trust --auth-local=trust, input=, env={}
'pg_createcluster --user=$(whoami) -l /home/beleswar/.msf4/db/log -d /home/beleswar/.msf4/db -s /tmp --encoding=UTF8 12 msf -- --username=$(whoami) --auth-host=trust --auth-local=trust' returned 0
Creating new PostgreSQL cluster 12/msf ...
/usr/lib/postgresql/12/bin/initdb -D /home/beleswar/.msf4/db --encoding UTF8 --username=beleswar --auth-host=trust --auth-local=trust
The files belonging to this database system will be owned by user "beleswar".
This user must also own the server process.

The database cluster will be initialized with locale "en_IN".
The default text search configuration will be set to "english".

Data page checksums are disabled.

fixing permissions on existing directory /home/beleswar/.msf4/db ... ok
creating subdirectories ... ok
selecting dynamic shared memory implementation ... posix
selecting default max_connections ... 100
selecting default shared_buffers ... 128MB
selecting default time zone ... Asia/Kolkata
creating configuration files ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... ok
syncing data to disk ... ok

Success. You can now start the database server using:

pg_ctlcluster 12 msf start

Warning: The parent /var/run/postgresql of the selected
stats_temp_directory is not writable for the cluster owner. Not adding this
setting in postgresql.conf.
Ver Cluster Port Status Owner Data directory Log file
12 msf 5433 down beleswar /home/beleswar/.msf4/db /home/beleswar/.msf4/db/log
Starting database at /home/beleswar/.msf4/db...run_cmd: cmd=pg_ctlcluster 12 msf start -- -o "-p 5433" -D /home/beleswar/.msf4/db -l /home/beleswar/.msf4/db/log, input=, env={}
'pg_ctlcluster 12 msf start -- -o "-p 5433" -D /home/beleswar/.msf4/db -l /home/beleswar/.msf4/db/log' returned 0

success

Arch Output in debug mode:

./msfdb init
[?] Would you like to init the webservice? (Not Required) [no]: no
Clearing http web data service credentials in msfconsole
run_cmd: cmd=./msfconsole -qx 'db_disconnect --clear; exit', input=, env={}
'./msfconsole -qx 'db_disconnect --clear; exit'' returned 0
No default data service was configured.

Running the 'init' command for the database:
Creating database at /home/beleswar/.msf4/db
run_cmd: cmd=initdb --auth-host=trust --auth-local=trust -E UTF8 /home/beleswar/.msf4/db, input=, env={}
'initdb --auth-host=trust --auth-local=trust -E UTF8 /home/beleswar/.msf4/db' returned 0
The files belonging to this database system will be owned by user "beleswar".
This user must also own the server process.

The database cluster will be initialized with locale "en_US.UTF-8".
The default text search configuration will be set to "english".

Data page checksums are disabled.

fixing permissions on existing directory /home/beleswar/.msf4/db ... ok
creating subdirectories ... ok
selecting dynamic shared memory implementation ... posix
selecting default max_connections ... 100
selecting default shared_buffers ... 128MB
selecting default time zone ... Asia/Kolkata
creating configuration files ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... ok
syncing data to disk ... ok

Success. You can now start the database server using:

pg_ctl -D /home/beleswar/.msf4/db -l logfile start

run_cmd: cmd=pg_ctl -o "-p 5433" -D /home/beleswar/.msf4/db status, input=, env={}
'pg_ctl -o "-p 5433" -D /home/beleswar/.msf4/db status' returned 3
pg_ctl: no server running
Starting database at /home/beleswar/.msf4/db...run_cmd: cmd=pg_ctl -o "-p 5433" -D /home/beleswar/.msf4/db -l /home/beleswar/.msf4/db/log start, input=, env={}
'pg_ctl -o "-p 5433" -D /home/beleswar/.msf4/db -l /home/beleswar/.msf4/db/log start' returned 1
waiting for server to start.... stopped waiting
pg_ctl: could not start server
Examine the log output.
run_cmd: cmd=pg_ctl -o "-p 5433" -D /home/beleswar/.msf4/db status, input=, env={}
'pg_ctl -o "-p 5433" -D /home/beleswar/.msf4/db status' returned 3
pg_ctl: no server running
failed

Notice this block in the Ubuntu output,

Warning: The parent /var/run/postgresql of the selected
stats_temp_directory is not writable for the cluster owner. Not adding this
setting in postgresql.conf.

and the cmd = pg_createcluster & -s /tmp flag here

Running the 'init' command for the database:
Creating database at /home/beleswar/.msf4/db
run_cmd: cmd=pg_createcluster --user=$(whoami) -l /home/beleswar/.msf4/db/log -d /home/beleswar/.msf4/db -s /tmp --encoding=UTF8 12 msf -- --username=$(whoami) --auth-host=trust --auth-local=trust, input=, env={}

This verifies that Ubuntu uses the pg_ctlcluster.rb file which creates the socket at /tmp instead of the default location.

Whereas in Arch, notice this block, cmd = initdb

Running the 'init' command for the database:
Creating database at /home/beleswar/.msf4/db
run_cmd: cmd=initdb --auth-host=trust --auth-local=trust -E UTF8 /home/beleswar/.msf4/db, input=, env={}

This verifies that Arch uses the pg_ctl.rb helper which does not change the default socket location, hence the error.

Finally, As these changes are done in pg_ctlcluster.rb already, shouldn't we make them implemented in pg_ctl.rb as well (this PR) ? That would ensure that this issue isn't found again in any of the systems as we would have these 2 correct helpers only!

@3V3RYONE
Copy link
Contributor Author

3V3RYONE commented Feb 5, 2022

As a short summary of the above message, the logs can be examined for clear insight.

Ubuntu's postgresql log (/home/user/.msf4/db/log) :

cat log
2022-02-03 12:31:35.986 IST [28085] LOG:  starting PostgreSQL 12.9 (Ubuntu 12.9-0ubuntu0.20.04.1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0, 64-bit
2022-02-03 12:31:35.987 IST [28085] LOG:  listening on IPv4 address "127.0.0.1", port 5433
2022-02-03 12:31:36.531 IST [28085] LOG:  listening on Unix socket "/tmp/.s.PGSQL.5433"
2022-02-03 12:31:37.379 IST [28086] LOG:  database system was shut down at 2022-02-03 12:30:27 IST
2022-02-03 12:31:37.651 IST [28085] LOG:  database system is ready to accept connections
2022-02-03 12:32:29.569 IST [28085] LOG:  received SIGHUP, reloading configuration files

Arch's postgresql log (/home/user/.msf4/db/log) :

2022-02-04 12:09:38.804 IST [3143] LOG:  starting PostgreSQL 13.4 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 11.1.0, 64-bit
2022-02-04 12:09:38.806 IST [3143] LOG:  listening on IPv6 address "::1", port 5433
2022-02-04 12:09:38.806 IST [3143] LOG:  listening on IPv4 address "127.0.0.1", port 5433
2022-02-04 12:09:38.807 IST [3143] FATAL:  could not create lock file "/run/postgresql/.s.PGSQL.5433.lock": No such file or directory
2022-02-04 12:09:38.809 IST [3143] LOG:  database system is shut down

So, Ubuntu's ./msfdb init command is successful only because it's socket is created at /tmp (done by pg_ctlcluster.rb helper).

But for systems that use the pg_ctl.rb helper, like Arch and Windows, the ./msfdb init command fails, and these changes fixes it.

@3V3RYONE 3V3RYONE changed the title Fix msfdb init command failure in linux Fix msfdb init command failure in systems that use the 'pg_ctl.rb' msfdb_helper Feb 5, 2022
@jmartin-tech
Copy link
Contributor

Another note about this I recently came across, some hardened environments mount /tmp with noexec and nodev flags which could cause explicit designation of /tmp to fail on more systems.

@smcintyre-r7
Copy link
Contributor

Alright, I was able to confirm the original issue and that this patch fixes it. I'll approve it.

@jmartin-r7 are there any outstanding items that need to be tested now before this is merged?

/tmp shouldn't be explicitly designated any more, the temporary directory is selected by Ruby's Dir.tmpdir, though on the Arch system I tested with Dir.tmpdir is /tmp. If that's a scenario you're concerned about, how would you propose it be handled?

@3V3RYONE
Copy link
Contributor Author

3V3RYONE commented Feb 8, 2022

/tmp shouldn't be explicitly designated any more, the temporary directory is selected by Ruby's Dir.tmpdir, though on the Arch system I tested with Dir.tmpdir is /tmp. If that's a scenario you're concerned about, how would you propose it be handled?

As you said, /tmp isn't explicitly designated anymore, it is replaced by Ruby's Dir.tmpdir, which decides the temporary directory. But this is for pg_ctl.rb, the one which this PR changes.

The pg_ctlcluster.rb helper although, already seems to explicitly designate the /tmp directory. Is this what @jmartin-r7 may be pointing out?

run_cmd("pg_createcluster --user=$(whoami) -l #{@db.shellescape}/log -d #{@db.shellescape} -s /tmp --encoding=UTF8 #{@pg_version} #{@options[:msf_db_name].shellescape} -- --username=$(whoami) --auth-host=trust --auth-local=trust")

@smcintyre-r7 , do you suggest replacing the /tmp in the pg_ctlcluster.rb file, by Dir.tmpdir as well? probably this can enhance the pre-written code..

@smcintyre-r7 smcintyre-r7 removed their assignment Feb 22, 2022
@smcintyre-r7
Copy link
Contributor

Okay so if we could determine that /tmp isn't going to work because the dev object can't be created there are we thinking the script should still fail, or that we should come up with some other location automatically through some means to place the dev object?

@3V3RYONE
Copy link
Contributor Author

3V3RYONE commented Feb 23, 2022

Hello!.. Sorry for being so late on this, I just finished my college exams and back after a long.

yes, so first it checks if /tmp is mounted with noexec flags. If it is not mounted, well and good, we change the default unix_socket_directories path to /tmp to complete the initialization. But if /tmp is mounted with noexec, there are two things that maybe done,

  1. Do not change the default unix_socket_directories = /run/postgresql location, and put up a message asking the user to try sudo ./msfdb init if the current initialization fails. That will let the sudo initialization successful because /run/postgresql can now be accessed with sudo privileges.

  2. Check any other locations which is not mounted with noexec flags, and replace unix_socket_directories with that location. This would not need sudo privileges to complete the initialization.

I have implemented point 1 in the latest commit. The 2nd one may seem more optimal. However it comes with it's one drawbacks :

  1. We have to ensure that the picked directory can again be accessed without sudo privileges. Else it indirectly leads to the issue that this PR aims to fix.
  2. The /tmp directory holds the files temporarily only, and flushes all of them in a new session. But it wont happen in the other locations that we pick up. We kind of are saving the socket file in that location permanently.

So I just wanted to ask your opinions. Which one should we implement? or any other suggestions?

@3V3RYONE
Copy link
Contributor Author

3V3RYONE commented Feb 23, 2022

IMHO, most users would not specifically partition a /tmp disk while installing an OS. /tmp just comes under the root filesystem automatically and is accessible by all processes. And generally while installing any software, their executables are placed in the /tmp only (unless you explicitly mention another location). Even while running bundle install for MSF would require to place some executables in /tmp.

So for users that specifically partition their /tmp directory and mount it with noexec flags, isn't it kinda on them that they are deciding not to install/initialize any softwares the easy way?

@jmartin-tech
Copy link
Contributor

I am not a fan of inspecting the mount command output as Dir.tmpdir is not guaranteed to be a mount point.

My thought here would be to test if we can create a device in Dir.tmpdir then fallback and test to creation in ~/.msf4/db_socket or similar path in ~/.msf4. If neither is valid then raising an error about not being able to create a socket seems a reasonable failure condition.

I believe mkfifo or possibly mknod should result in an exit code error if the filesystem is mounted nodev. This needs to be testing though as I have not verified this. An important part of this is validation on some common distros and using a commonly available test.

@3V3RYONE
Copy link
Contributor Author

Sure, thats a good idea, I'll work on it

@meditant
Copy link

Hello,
Same problem if I run msfconsole the database is not created but if you run msfconsole, say NO to the creation of the database and after executing msfdb init it works, the database is created!

@3V3RYONE
Copy link
Contributor Author

Hello, Same problem if I run msfconsole the database is not created but if you run msfconsole, say NO to the creation of the database and after executing msfdb init it works, the database is created!

Does running msfconsole ask for the database service? I haven't encountered it yet. Can you please share your msfconsole output or a snapshot showing the steps! Thanks :)

@3V3RYONE
Copy link
Contributor Author

Hey @jmartin-r7 , I tried to look on the commands you said like mknod or mkfifo or even makedev. But I couldn't find any special error code returned by them when the partition(/tmp) is mounted with nodev flags.

Infact, the commands executed successfully to create a device in the /tmp directory, even when it's mounted with nodev, noexec flags.

Snapshot of output in Arch VM:

output_mknod

Do you have any suggestions? or other specific commands?

@gwillcox-r7
Copy link
Contributor

@jmartin-r7 Any update on this? This PR is currently blocked on your feedback.

@jmartin-tech
Copy link
Contributor

I am looking for a good pattern for detection, as is the pattern here is unreliable. Per my previously stated feedback.

@gwillcox-r7 gwillcox-r7 added the blocked Blocked by one or more additional tasks label Apr 13, 2022
@smcintyre-r7
Copy link
Contributor

Can we move this forward with the partial fix for the most common cases since this issue is still affecting people while we continue to investigate a solution for all of the edge cases?

@jmartin-tech
Copy link
Contributor

The change here could impact working environments. Just adding the path to initial config startup is somewhat reasonable however the original issue is already an edge case for Arch which does not use our installers anyway.

One issue here is usage of the database helpers when not in a supported OS. Landing as is could expand the issue and introduce impact to various supported platforms.

Looking at possible testing patterns since this is performed only on db creation maybe the path forward is to create a test executable file and a test devices and interact with them as part for the decision processing for selecting location.

jmartin@ubuntu:~/loopfs/test$ ls -lah
total 12K
drwxr-xr-x 2 jmartin root    4.0K May 31 16:22 .
drwxr-xr-x 4 root    root    4.0K May 31 16:21 ..
-rwxrwxr-x 1 jmartin jmartin   22 May 31 16:22 test
jmartin@ubuntu:~/loopfs/test$ cat test
#!/bin/bash
echo exec
jmartin@ubuntu:~/loopfs/test$ ./test
-bash: ./test: Permission denied
jmartin@ubuntu:~/loopfs/test$ mount | grep loop
/dev/loop10 on /home/jmartin/loopfs type ext4 (rw,nodev,noexec,relatime)

This checks out for testing the noexec using a simple file write and attempt to execute.

I will see if there is more we can add to the nodev options since this needs to be able to create a socket device.

@3V3RYONE
Copy link
Contributor Author

3V3RYONE commented Jun 1, 2022

Hey, thank you so much for the suggestion above. I was also looking for a good pattern to get this one landed.

So, we can attempt to execute a write file for checking the noexec part. Now just looking for a similar approach for identifying nodev on the Dir.tmpdir partition. I'll work on the first part currently! Thanks :D

@3V3RYONE
Copy link
Contributor Author

Hello, I've added the commit to check if the Dir.tmpdir directory is mounted with NOEXEC flags, before creating the socket file in it. If Dir.tmpdir is mounted with NOEXEC, it tries to create the socket file in ~/.msf4/db. If it fails too, the db initialization procedure fails.

Testing result in Arch Linux when Dir.tmpdir is mounted with NOEXEC: DB socket is created at ~/.msf4/db. Successful.

[beleswar@archvm metasploit-framework]$ ./msfdb init
[?] Would you like to init the webservice? (Not Required) [no]: 
Clearing http web data service credentials in msfconsole
Running the 'init' command for the database:
Creating database at /home/beleswar/.msf4/db
Creating db socket file at /home/beleswar/.msf4/db
Starting database at /home/beleswar/.msf4/db...success
Creating database users
Writing client authentication configuration file /home/beleswar/.msf4/db/pg_hba.conf
Stopping database at /home/beleswar/.msf4/db
Starting database at /home/beleswar/.msf4/db...success
Creating initial database schema
Database initialization successful

Testing result in Arch Linux when Dir.tmpdir is not mounted with NOEXEC: DB socket is created at /tmp. Successful.

[beleswar@Som metasploit-framework]$ ./msfdb init
[?] Would you like to init the webservice? (Not Required) [no]: 
Clearing http web data service credentials in msfconsole
Running the 'init' command for the database:
Creating database at /home/beleswar/.msf4/db
Creating db socket file at /tmp
Starting database at /home/beleswar/.msf4/db...success
Creating database users
Writing client authentication configuration file /home/beleswar/.msf4/db/pg_hba.conf
Stopping database at /home/beleswar/.msf4/db
Starting database at /home/beleswar/.msf4/db...success
Creating initial database schema
Database initialization successful

Testing result in Ubuntu when Dir.tmpdir is not mounted with NOEXEC: DB socket is created at /tmp (uses pg_ctlcluster.rb helper). Successful.

beleswar@beleswar:~/metasploit-framework$ ./msfdb init
[?] Would you like to init the webservice? (Not Required) [no]: 
Clearing http web data service credentials in msfconsole
Running the 'init' command for the database:
Creating database at /home/beleswar/.msf4/db
Starting database at /home/beleswar/.msf4/db...success
Creating database users
Writing client authentication configuration file /home/beleswar/.msf4/.local/etc/postgresql/12/msf/pg_hba.conf
Creating initial database schema
Database initialization successful

Copy link
Contributor

@jmartin-tech jmartin-tech left a comment

Choose a reason for hiding this comment

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

The added exec test is great, I added some suggestions on adjusting cleanup as well as encapsulate the functionality a bit.

Comment on lines 46 to 59
def test_executable_file(path)
File.open("#{path}/msfdb_testfile", 'w') do |f|
f.puts "#!/bin/bash\necho exec"
end
File.chmod(0744, "#{path}/msfdb_testfile")

if run_cmd("#{path}/msfdb_testfile")
File.open("#{@db}/postgresql.conf", 'a') do |f|
f.puts "unix_socket_directories = \'#{path}\'"
end
@socket_directory = path
puts "Creating db socket file at #{path}"
end
end
Copy link
Contributor

@jmartin-tech jmartin-tech Jun 23, 2022

Choose a reason for hiding this comment

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

Instead of using a side-effect to set @socket_directory consider adjusting this method to return a boolean result.

Also an ensure block should be added to remove the temporary file.

Suggested change
def test_executable_file(path)
File.open("#{path}/msfdb_testfile", 'w') do |f|
f.puts "#!/bin/bash\necho exec"
end
File.chmod(0744, "#{path}/msfdb_testfile")
if run_cmd("#{path}/msfdb_testfile")
File.open("#{@db}/postgresql.conf", 'a') do |f|
f.puts "unix_socket_directories = \'#{path}\'"
end
@socket_directory = path
puts "Creating db socket file at #{path}"
end
end
def test_executable_path(path)
file_name = File.join(path, 'msfdb_testfile')
File.open(file_name, 'w') do |f|
f.puts "#!/bin/bash\necho exec"
end
File.chmod(0744, file_name)
if run_cmd(file_name)
File.open("#{@db}/postgresql.conf", 'a') do |f|
f.puts "unix_socket_directories = \'#{path}\'"
end
@socket_directory = path
puts "Creating db socket file at #{path}"
end
return true
rescue => e
return false
ensure
begin
File.delete(file_name)
rescue
print_error("Unable to delete test file #{file_name}")
end
end

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the suggestions! Helped me make the code more normalized!
I've added the commit and tested again on Arch (with NOEXEC and without NOEXEC) and Ubuntu. It works fine!

Copy link
Contributor

@jmartin-tech jmartin-tech left a comment

Choose a reason for hiding this comment

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

Current state addresses most of my concerns.

@gwillcox-r7 gwillcox-r7 self-assigned this Jul 27, 2022
@gwillcox-r7 gwillcox-r7 removed the blocked Blocked by one or more additional tasks label Jul 27, 2022
@gwillcox-r7
Copy link
Contributor

Before Patch:

image

@gwillcox-r7
Copy link
Contributor

After patch:

image

@gwillcox-r7
Copy link
Contributor

gwillcox-r7 commented Jul 27, 2022

LGTM will land this as soon as tests pass 👍

@gwillcox-r7 gwillcox-r7 merged commit 09ea057 into rapid7:master Jul 27, 2022
@gwillcox-r7
Copy link
Contributor

gwillcox-r7 commented Jul 27, 2022

Release Notes

A bug has been fixed in the pg_ctl.rb helper whereby it was possible that initializing and starting databases using msfdb init might fail due to the pg_ctl.rb helper not properly setting unix_socket_directories to a path that a non-root user can write to. This code has now been updated so that it will set the unix_socket_directories setting to a path that the current user can write to or will error out if it cannot find a writeable directory to use for the socket file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug database rn-fix release notes fix
Projects
None yet
Development

Successfully merging this pull request may close these issues.

msfdb init command fails to start database service in linux
6 participants