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

[Bug] sslocal cannot daemonize when a plugin is specified via config file #640

Closed
spyophobia opened this issue Oct 6, 2021 · 20 comments
Closed

Comments

@spyophobia
Copy link
Contributor

Bug description

sslocal cannot daemonize using the -d or --daemonize flag when the config file specifies a plugin.

I am not entirely sure that this is 100% the real issue here, but I have tested on two different OSes (RHEL8 & Arch) and observed the same behavior, so let's just say I am reasonably confident.

To replicate

Here I will use simple-tls as an example, but the choice of plugin seems irrelevant.
On a Linux x64 machine:

  1. Get required binaries - sslocal v1.11.2 (latest stable at the time of writing), simple-tls or any other plugin
  2. Get the config files: configs.zip
  3. Verify that when -d is not used, sslocal runs correctly
# Both should run fine
sslocal -v -c ss.json
sslocal -v -c ss-plugin.json # you might have to edit plugin path
  1. Verify that when -d is used without a plugin, sslocal daemonizes correctly
sslocal -v -c ss.json -d --daemonize-pid ss.pid
ps aux | grep sslocal # expect sslocal is running
  1. Verify that when -d is used with a plugin, sslocal no longer daemonizes
sslocal -v -c ss-plugin.json -d --daemonize-pid ss-plugin.pid
ps aux | grep sslocal # expect no sslocal process

Environment & versions

uname -msr
Linux 4.18.0-305.19.1.el8_4.x86_64 x86_64

sslocal --version
shadowsocks 1.11.2

whoami
root
@zonyitoo
Copy link
Collaborator

zonyitoo commented Oct 7, 2021

Couldn't reproduce with 1.12.0-alpha.8 and v2ray-plugin.

What did you see in logs when daemonizing sslocal with plugins? Did you try other plugins?

@spyophobia
Copy link
Contributor Author

Previously I had tried two plugins. I just tried using v2ray a moment ago and daemonizing also failed. This is on v1.11.2.

When daemonizing sslocal with plugin:

  • there were no logs whatsoever on the terminal when daemonizing fails
  • the exit code is 0
  • pid file is created
  • there is no indication to whether the daemon is running or not without using ps aux | grep sslocal.

Things I tried/checked

  • test on RHEL & Arch, same result
  • SELinux off (setenforce 0)
  • running as root & non-root

An interesting observation: when daemonizing as non-root with or without a plugin, the pid file is created but is left empty. There shouldn't be a problem with file permissions as all this is happening in my home directory. Not sure how this may be related to this issue but FYI.


I will try the latest git version and report back if you so desire.

@spyophobia
Copy link
Contributor Author

Also can you please share your working config file? Just to rule out the possibility of some unknown config conflicts.

@zonyitoo
Copy link
Collaborator

zonyitoo commented Oct 7, 2021

Also can you please share your working config file? Just to rule out the possibility of some unknown config conflicts.

I used the least working configuration with only required fields.

@spyophobia
Copy link
Contributor Author

Would you consider this a minimal config?

{
  "servers": [
    {
      "address": "foo.bar",
      "port": 8800,
      "password": "foobar",
      "method": "chacha20-ietf-poly1305",
      "plugin": "./v2ray",
      "plugin_opts": "tls;host=foo.bar"
    }
  ],
  "local_port": 1080
}

I ran sslocal -c ss-plugin.json -v -d --daemonize-pid "./ss.pid" and the daemon failed silently.

@spyophobia
Copy link
Contributor Author

spyophobia commented Oct 7, 2021

I just tried with the latest git version (edfea66 (HEAD -> master, tag: v1.12.0-alpha.8, origin/master, origin/HEAD) release v1.12.0-alpha.8 with identical results.
I built and ran sslocal with cargo directly: cargo run --release --bin sslocal -- -c ss-plugin.json -v -d --daemonize-pid ss.pid

Trying to increase the verbosity level with -vvv does not produce any terminal logs either. It's kind of expected but worth I try I guess.

@dev4u
Copy link

dev4u commented Oct 7, 2021

机器的内存多大?

@spyophobia
Copy link
Contributor Author

$ free -h
               total        used        free      shared  buff/cache   available
Mem:            31Gi       5.8Gi       8.8Gi       2.5Gi        16Gi        22Gi
Swap:           31Gi          0B        31Gi

@dev4u
Copy link

dev4u commented Oct 7, 2021

cargo run --release --bin sslocal -- -c ss-plugin.json -v -d --daemonize-pid ss.pid
我不确定你的命令写得是否正确,但是nix系统在读取参数指定的文件,如果没有指定具体的路径,例如./,那么在加载时是有读取目录优先级的,不会读取当前路径的配置。

如果要读取当前目录,难免歧义,显式指定./是很有必要。读取插件程序,我建议用绝对路径,难免运行在nobody下,会因为相对路径而导致读取失败。

@spyophobia
Copy link
Contributor Author

@dev4u Thank you for the tips but I am more than proficient in using Bash shell, and I know for a fact that my files are referenced correctly.

What you said applies only in the case of executable binary lookup - for example when running ./my_bin when there is a my_bin elsewhere in a directory included in $PATH. If I run my_bin my.conf, there is no such lookup happening with my.conf (unless my_bin is explicitly written to do so, as in the case of sudo or watch), which means it's equivalent to my_bin ./my.conf. In the case of my command:

  • there is only one globally installed cargo
  • ss-plugin.json is a file in $(pwd); sslocal would error and exit if it is not found
  • ss.pid is correctly generated under $(pwd)

If you read my earlier posts you would see that the only variable changed was whether -d --daemonize-pid <file> was included. Without it, sslocal launches correctly; with it, sslocal no longer launches. Hopefully this will convince you that this has nothing to do with the way I specify file paths in CLI.

@spyophobia
Copy link
Contributor Author

Also, when sslocal daemonizes correctly (if the config file does not specify a plugin), the process is run as the current user, not nobody. You can confirm this with ps aux | grep sslocal.

@zonyitoo
Copy link
Collaborator

zonyitoo commented Oct 7, 2021

Would you consider this a minimal config?

{
  "servers": [
    {
      "address": "foo.bar",
      "port": 8800,
      "password": "foobar",
      "method": "chacha20-ietf-poly1305",
      "plugin": "./v2ray",
      "plugin_opts": "tls;host=foo.bar"
    }
  ],
  "local_port": 1080
}

I ran sslocal -c ss-plugin.json -v -d --daemonize-pid "./ss.pid" and the daemon failed silently.

I can reproduce with this config.

You can find the reason by running sslocal with --log-config configs/log4rs.yml to redirect logs into shadowsocks.log file:

2021-10-08T00:01:30.054116+08:00 ERROR failed to daemonize, Chroot(1) (unable to chroot into directory)
2021-10-08T00:01:30.054350+08:00 INFO  shadowsocks local 1.12.0-alpha.8 build 2021-10-07T15:58:46.785327+00:00

Errno 1 is EPERM.

So setting "plugin" with relative path will cause daemonize failed. Should I remove the chroot call?

zonyitoo added a commit that referenced this issue Oct 7, 2021
zonyitoo added a commit that referenced this issue Oct 7, 2021
@zonyitoo
Copy link
Collaborator

zonyitoo commented Oct 7, 2021

You may have to use absolute path to plugin, or let process to find v2ray in $PATH.

@spyophobia
Copy link
Contributor Author

Okay, it seems like I have actually discovered two closely related but different issues here.

The first one is exactly as you stated: if "plugin" field contains a relative path, daemonizing will fail.

The second one is the same problem but with "plugin_opts". If the plugin requires passing in a path to a config file using this field (e.g. Cloak) and we use a relative path, daemonizing will also fail.

Commit 862a8a7 did fix the first issue, but not the second one. I assume you reverted the change due to backward compatibility concerns?

I think the greater problem here is the difference in behavior between "daemon mode" and "non-daemon mode". The same config file can work in one mode but fail in another, which is unexpected and hard to diagnose.

@zonyitoo
Copy link
Collaborator

zonyitoo commented Oct 7, 2021

I assume you reverted the change due to backward compatibility concerns?

Nope. It didn't fix the first issue at all.

You could try to apply that commit locally and see if that fixes your issue?

@spyophobia
Copy link
Contributor Author

spyophobia commented Oct 7, 2021

Weird, I just compiled and tested at 862a8a7 again and indeed it didn't fix anything. I must have tested with the wrong config file last time.

As far as I understand, chroot merely restricts the directories the binary can access, without necessarily doing anything to the working directory. I took a look at daemonize's documentation and found Daemonize::working_directory, which is what I think we need here. Something like this?

use std::{env::current_dir, path::Path};
let pwd = current_dir()
        .expect("bad pwd")
        .canonicalize()
        .expect("cannot canonicalize");
let mut d = Daemonize::new().umask(0).working_directory(pwd);

Excuse my bad error handling

Also I believe it is correct not to call chroot. With it I am getting ERROR failed to daemonize, Chroot(1) (unable to chroot into directory) in logs.

@zonyitoo
Copy link
Collaborator

zonyitoo commented Oct 7, 2021

Did you tried adding .working_directory(pwd)? Did that fix your issue?

@spyophobia
Copy link
Contributor Author

Yes it did. Just tested with v2ray and cloak, solved both issues (now both "plugin" and "plugin_opts" can use relative paths).

Would you like a PR?

@zonyitoo
Copy link
Collaborator

zonyitoo commented Oct 7, 2021

Go ahead.

zonyitoo pushed a commit that referenced this issue Oct 7, 2021
This is the default behavior when not daemonizing.
@zonyitoo zonyitoo closed this as completed Oct 7, 2021
@spyophobia
Copy link
Contributor Author

spyophobia commented Oct 7, 2021

By the way, you may wish to include a compatibility note on this in the next release, as I am concerned that this "fix" could break some people's configs.

In the original implementation, since Daemonize::working_directory was not called, the working directory defaults to /. So if someone has a plugin with path /opt/ss-plugins/my-plugin, but has set the "plugin" field in their config file as opt/ss-plugins/my-plugin (and assuming $(pwd) is not /), this change will break their config.

Admittedly, a path like this would be considered an "erroneous" config. Nevertheless this change breaks bug-compatibility so I think it's worth a mention.

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

No branches or pull requests

3 participants