2

Original problem: on my MacBook Pro, the video camera keeps freezing. Found the solution is to type

sudo killall VDCAssistant

in a terminal, and provide my password (I have admin privileges).

Now I would like to make this a single command (that doesn't need a password). I found that it is possible to use visudo to edit a file that tells sudo what commands might be run without requiring a password. Obviously it would be too dangerous to make killall itself "password free", so I was hoping I could wrap the specific command inside another script; make that script "password free"; and get on with my life. So here is what I did:

Create a file called video, which contains

!#/bin/bash
sudo killall VDCAssistant

Put it in /usr/local/bin, give permissions chmod 700 video, and rehash. When I type video, I get prompted for my password. So far so good.

Next, I ran visudo and added the lines

User_Alias USERS = myusername
CMND_Alias CMDS = /usr/local/bin/video
USERS ALL = (ALL) NOPASSWD: CMDS

But that doesn't have the desired effect. I am still prompted for the password. If I make root the owner of the script, it doesn't change things. If I leave out the sudo part of the command, it tells me "No matching processes belonging to you were found".

Does anyone have a trick that I missed - how do I achieve my goal (using a single-word command to perform the killall for a process I do not own, without having to type my password)?

I have read the answers to how to run script as another user without password but could not find anything that applied here; I also read the answers to sudo with password in one command line - that is where the inspiration to use visudo came from - but again it didn't give me the answer I was looking for. Obviously I can't save my password in plain text, and I don't want to remove the "normal" protections from killall.

Is there a way to do this?

Floris
  • 45,857
  • 6
  • 70
  • 122

3 Answers3

2

If you have an actual binary executable, you can set the setuid bit on it using chmod 4755, and then the binary will always execute as whichever user owns it (most useful if it is root, obviously). However, this doesn't work on shell scripts, for security reasons. I'm not 100% sure that this is the case, but it is possible that visudo may also be ignoring shell scripts for the same reasons.

If you've got Xcode installed, though, you can build an actual binary program to run the killall command, and then you can set the setuid bit on it. This Swift program should do the trick:

import Foundation

guard setuid(0) == 0 else {
    print("Couldn't set UID 0: error \(errno)")
    exit(-1)
}

let killall = Process()

killall.launchPath = "/usr/bin/killall"
killall.arguments = ["VDCAssistant"]

killall.launch()
killall.waitUntilExit()

Save the above in a text file called video.swift. Run the following command:

swiftc video.swift -framework Foundation

This will create a file called video in that directory. Move the file to /usr/local/bin, change the owner to root and setuid that sucker:

mv video /usr/local/bin
sudo chown root:wheel /usr/local/bin/video
sudo chmod 4755 /usr/local/bin/video

Now it should work.

If you want to get fancier, it would probably also be possible to rig up a plist in /Library/LaunchDaemons and get launchd to automatically kill the process every so often.

Community
  • 1
  • 1
Charles Srstka
  • 16,665
  • 3
  • 34
  • 60
  • This looks promising - I will try it and let you know. I am still hoping there is an easier solution... Running a cron to keep killing the process would probably freeze my video during the restart - so that's not so great. But an application with a keyboard shortcut... I could live with that. – Floris Aug 21 '17 at 23:54
  • 1
    Once compiled, the Swift program above would be functionally equivalent to the shell script in terms of using it; just call it on the command line. The only difference will be in the initial setup, since you'll have to compile the code. That's just a matter of running `swiftc sourcefile.swift -framework Foundation`, though. – Charles Srstka Aug 21 '17 at 23:59
  • I followed your instructions, and compiling the binary worked great. The solution - not so much: `>video No matching processes belonging to you were found > sudo video` (no error message because it works) `> ls -l video -rwsr-xr-x 1 root admin 43724 Aug 22 10:58 video` Shows `s` bit is set. So even with `setuid` it seems to insist that I am not "root enough" for this... Should I change the group, perhaps? Note - I made a typo in my question, it's `VDCAssistant` not `VCDAssistant`. I have edited my question and your answer accordingly (not to confuse others). – Floris Aug 22 '17 at 15:39
  • Ah, tested it on my machine, and it seems sudo needs the real UID to be 0, not just the effective UID. I'll edit the answer and add a call to setuid(), which will probably fix it (it seems to on my machine). Try it out for size and let me know if it works. – Charles Srstka Aug 22 '17 at 15:45
  • Yes - that additional action (`guard setuid(0) ==0`) did the trick. Well done, and thank you for your help. I will leave the bounty open for now... since the "rep is spent" I might as well get some benefit of the additional traffic. Perhaps someone can give me an explanation of where the subtlety (real vs effect uid) comes from and why it affects this particular issue. I am going to edit in the additional steps needed to fully implement this solution. – Floris Aug 22 '17 at 16:14
  • A pretty good description of real vs. effective UID, and the purpose of each, can be found here: https://stackoverflow.com/questions/32455684/difference-between-real-user-id-effective-user-id-and-saved-user-id The man page for `setuid` `man 2 setuid`) is a useful resource as well. The reason it affected the issue was because it seems `killall` is using the real UID as the search parameter when looking for processes owned by you. – Charles Srstka Aug 22 '17 at 16:30
  • "`killall` is using the real UID as the search parameter" is probably the key. You have been so helpful. Thank you. I am ready to award the bounty - but can't do it yet... apparently there is some timer on it. – Floris Aug 22 '17 at 16:38
2

I'm a total bash noob but try checking this answer, the problem here might be with your sudoers file. I tried replicating your problem and encountered the same behaviour, with this answer I was able to make it work. I can't say anything about safety of this solution. To sum up:

  • I've created a file called video while logged in as root with following contents:

    #!/bin/bash
    sudo killall VDCAssistant
    
  • set it's permissions chmod 700 video and rehashed

  • using visudo I've added a following line mysuername ALL=NOPASSWD:ALL below the line %sudo ALL=(ALL:ALL) ALL since myusername is a member of group sudo
  • sudo video runs the script and kills the process
  • by adding alias VIDEO 'sudo video' to your .cshrc file, you can make this a true "one word command"
Floris
  • 45,857
  • 6
  • 70
  • 122
mspaint
  • 21
  • 1
  • 4
  • 1
    Thanks for your suggestion. I believe that the edit you have in mind would make ANY command able to be run with "no password" and that is something I really want to avoid. I don't even want to be able to run `killall` without a password - only `killall VCDAssistant`. – Floris Aug 21 '17 at 23:52
  • Try `myusername ALL=NOPASSWD:/usr/local/bin/video` then. I am not able to run `killall` or even `killall VCDAssistant` without a password but I can succesfully run `sudo video` without it. – mspaint Aug 22 '17 at 00:02
  • I found that troubleshooting this is hampered by the fact that `sudo` has a timeout on the password -making it look like this solution works when it doesn't. When I added the line `Defaults timestamp_timeout = 0` to the `sudoers` file, this solution stopped "working". Any more thoughts? – Floris Aug 22 '17 at 14:55
  • It's weird. I've set up a macos 10.12 vm to test it since up to this moment I was using debian so I thought there might be some differences but everything works for me. Even with `Defaults timestamp_timeout = 0` I am able to run `sudo video` without sudo asking me for password. Looks like something is cancelling the `myusername ALL=NOPASSWD:/usr/local/bin/video` out. Maybe try creating a new user and see if it works for him? – mspaint Aug 22 '17 at 20:18
  • Ah - are you saying I have to type `sudo video` to get this to work... I was using `video`. Going to give this another shot (although I am pretty happy with the solution outlined in Charles' answer, and the embellishment I noted in the answer I added myself...) – Floris Aug 22 '17 at 20:21
  • Yes - this now works as advertised. Now I am torn ... two very nice solutions, with one being what I asked for and the other teaching me a whole new world. Also - by doing `alias VIDEO "sudo video"` I am able to reduce this to just typing one word - and I should be able to integrate it into an Automator script to make it a Service, just like I did for the other one. I now prefer this solution since it doesn't involve messing with binaries (although I am discovering that Swift is very cool). – Floris Aug 22 '17 at 20:26
1

The answer by Charles Srstka solved my immediate problem; subsequent clarifications by @mspaint showed that the script-based answer could in fact also be made to work. I then discovered a nice additional touch that I wanted to share for anyone that runs into this problem. This is "how do you make the whole process seamless".

1) "Hide" the killall command in a script:

Create a text file with the following lines:

#!/bin/bash
sudo killall VDCAssistant

and save it to /usr/local/bin/restartVideo.sh

Change the permissions: chmod 700 restartVideo.sh so only you can run it.

2) make the restartVideo.sh script password-free for use with sudo

Next we need to edit the sudoers file so there is no need to prompt for the password when running the above script. The editing is done by sudo visudo - this edits a copy of the file in vim, and checks it for errors before overwriting the original. Add the following lines to the file:

# my user can run the restartVideo.sh command with sudo without password:
User_Alias ME = myUserName
Cmnd_Alias VIDEO = /usr/local/bin/restartVideo.sh
ME ALL = (ALL) NOPASSWD: VIDEO

This last line actually means "user 'ME' (defined with the User_Alias command), from 'ALL' terminals, and executing as any member of '(ALL)', can without password (NOPASSWD:) execute all commands defined by VIDEO. Save the file - make sure there are no errors / warnings! N.B.: in my original question it appears I had used CMND_Alias instead of Cmnd_Alias; that must have generated a warning that I overlooked ... and it may be the reason my original approach wasn't working (???).

3) Create a Service to invoke the script For this I turned to the Mac Automator application - in particular, I created a new Service.

enter image description here

Search for the actions that have "run" in their title, and pick "Run shell script":

enter image description here

Using the default shell, run the command sudo /usr/local/bin/restartVideo.sh, taking no inputs. Make the command available to any application.

Finally - save the script as "Restart video".

And by miracle, you get the following menu item (showing it here for Skype):

enter image description here Now you can just select that menu item, and your camera comes back to life.

Floris
  • 45,857
  • 6
  • 70
  • 122