This is one of those "it depends" situations. According to the Microsoft documentation (found here https://docs.microsoft.com/en-us/previous-versions/dotnet/framework/data/adonet/sql/managing-permissions-with-stored-procedures-in-sql-server), it states:
Stored procedures take advantage of ownership chaining to provide access to data so that users do not need to have explicit permission to access database objects. An ownership chain exists when objects that access each other sequentially are owned by the same user. For example, a stored procedure can call other stored procedures, or a stored procedure can access multiple tables. If all objects in the chain of execution have the same owner, then SQL Server only checks the EXECUTE permission for the caller, not the caller's permissions on other objects. Therefore you need to grant only EXECUTE permissions on stored procedures; you can revoke or deny all permissions on the underlying tables.
Things that are key in that are that the OWNER of the objects needs to be the same. If the owner of the stored procedure is not the same owner as the table, then you will need to give explicit permissions on the table.
Also, if I remember right, if explicit permissions are granted on the object (ie table), they will override the ownership chaining. What I mean is if the table has DENY SELECT to the user and the stored procedure has GRANT EXECUTE to the user, the user will hit an error when they need to SELECT from the table. There are other exceptions such as with dynamic SQL in a stored procedure, or I hit one recently where a stored procedure called another stored procedure and the second stored procedure tried to truncate a table. I think you may also hit snags with cross-database queries.
I think there may also be exceptions if you have an EXECUTE AS in your stored procedure.
A good writeup on ownership chaining (with examples you can run on your own non-production SQL Instance) can be found here: